-
Notifications
You must be signed in to change notification settings - Fork 19
First pass at adding type annotations #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! I added feedback below, but more generally it looks like pre-commit has some feedback about the general formatting and linting. The formatting is easily fixed by running it as a command line tool (`--pre-commit run --all-files will do fine) and committing those changes. I've added the linting changes along with the other comments!
I tried to be thorough in the explanations of changes for context, but let me know if anything is unclear, or if more information would be helpful!
@@ -32,8 +32,16 @@ | |||
__version__ = "0.0.0+auto.0" | |||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_AVRprog.git" | |||
|
|||
import digitalio |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're just using something for typing like this library or the microcontroller
import below, you can put that in the try
/except
block guarded by a first import of typing
. The logic is as follows:
CircuitPython actually doesn't care about type annotation syntax as a method of saving space, and likewise doesn't store the information gleaned by parsing it. The effect is that it's basically anything goes, even having unbound types (using a variable that hasn't been assigned). Note that this only works in CircuitPython - CPython does care, and will get very mad if it can't resolve a type annotation.
So, in order to save RAM on the microcontroller, we avoid import items that only get used as a part of type annotation since that information isn't used or stored anyway. The way this is achieved is by using the typing
import - if we're in a microcontroller environment, it fails and skips importing anything else in the block. But if it's Blinka, it will succeed and continue to import anything else in that block that is required for type annotations.
So! Since this and the following are in that category of inputs, let's put them after the import of typing
in that block.
from digitalio import Direction, DigitalInOut | ||
|
||
try: | ||
import typing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pylint
warns against unused imports, since it figures you can just remove it. However, this goes against our strategy of using it as a sort of import guard, so in cases where we're actually not importing anything from typing
, we can disable the pylint
warning with a comment it parses:
import typing # pylint: disable=unused-import
However, I have some suggestion below that will make use of things imported from typing
, so we won't need to use that strategy in the end.
@@ -89,7 +97,7 @@ class Boards: | |||
_spi = None | |||
_rst = None | |||
|
|||
def init(self, spi_bus, rst_pin): | |||
def init(self, spi_bus, rst_pin: microcontroller.Pin) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spi_bus
is type busio.SPI
, the tell is how it can use the configure()
method, and busio
imports are common for the drivers.
@@ -100,7 +108,7 @@ def init(self, spi_bus, rst_pin): | |||
self._rst.direction = Direction.OUTPUT | |||
self._rst.value = True | |||
|
|||
def verify_sig(self, chip, verbose=False): | |||
def verify_sig(self, chip: dict, verbose: bool = False) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The typing for chip
is correct and valid, but Python also allows for the use of generics, which allow you to specify the content of some types. In this case, we can specify the types of the keys and values of the dict.
To do this for Python 3.7+ (earliest Python supported by Blinka), we can import Dict
from the typing
module and use brackets to specify the key and value types. You can see an example of a dict that gets passed in here:
All of the keys are str
, so that will be that. For the values, it's a little trickier. We could use a Union
to say it can take str
, list
, etc., but that's a bit of a clutter. The other option is to specify values as typing.Any
which is kinda like saying "look, don't count on this being any specific type, it could be anything at all, so don't try to lint it or try to use it unless you know what it is". That's probably a good fit for this use case.
With all of that said, after import Dict
and Any
from typing
, we can type it as:
def verify_sig(self, chip: dict, verbose: bool = False) -> bool: | |
def verify_sig(self, chip: Dict[str, Any], verbose: bool = False) -> bool: |
I won't comment for every instance where this applies but you can do this for all of them!
@@ -114,7 +122,7 @@ def verify_sig(self, chip, verbose=False): | |||
return False | |||
return True | |||
|
|||
def program_file(self, chip, file_name, verbose=False, verify=True): | |||
def program_file(self, chip: dict, file_name, verbose=False, verify=True) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at one of the examples, it looks like filename
is type str
. Don't forget to add verbose
and verify
as well!
@@ -262,7 +270,7 @@ def write_fuses(self, chip, low=None, high=None, ext=None, lock=None): | |||
|
|||
# pylint: enable=unused-argument,expression-not-assigned | |||
|
|||
def verify_fuses(self, chip, low=None, high=None, ext=None, lock=None): | |||
def verify_fuses(self, chip: dict, low=None, high=None, ext=None, lock=None) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the other arguments can be Optional[int]
here as well.
""" | ||
End programming mode: SPI is released, and reset pin set high. | ||
""" | ||
self._spi.unlock() | ||
self._rst.value = True | ||
|
||
def read_signature(self): | ||
def read_signature(self) -> list: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also define the contents of a list using generics. For lists, we only need to define what any given element can be. The return from _transaction()
is bytearray
, and an element of a bytearray
is an int
. Since we're appending only int
to the list, we can import List
from `typing and type the return here as:
def read_signature(self) -> list: | |
def read_signature(self) -> List[int]: |
@@ -317,7 +325,7 @@ def read_signature(self): | |||
sig.append(self._transaction((0x30, 0, i, 0))[2]) | |||
return sig | |||
|
|||
def read(self, addr, read_buffer): | |||
def read(self, addr: int, read_buffer: bytes) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're writing things into what's provided into read_buffer
, we'll need to use a type that can support writing using the indices. In this case, let's use bytearray
, which supports that. Sometimes we can use an even less specific type, but given how it's used, I do think that only bytearray
will work.
@@ -345,7 +353,7 @@ def _flash_word(self, addr, low, high): | |||
self._transaction((0x40, addr >> 8, addr, low)) | |||
self._transaction((0x48, addr >> 8, addr, high)) | |||
|
|||
def _flash_page(self, page_buffer, page_addr, page_size): | |||
def _flash_page(self, page_buffer: float, page_addr: int, page_size: int): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like page_buffer
needs to support access by indices and that individual elements are int
. Looking at how it gets used in the internals, it looks like it expects a bytearray
. You can also add the None
return type!
@@ -375,7 +383,7 @@ def _busy_wait(self): | |||
pass | |||
|
|||
|
|||
def read_hex_page(file_state, page_addr, page_size, page_buffer): | |||
def read_hex_page(file_state: dict, page_addr: int, page_size: int, page_buffer: bytearray): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can add the return type here as well, which appears to be bool
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, meant to click "Request changes"!
annotations are now merged in from #34 |
This is my first time updating a library for type annotations so it could probably use a thorough review.
I'm also stuck on the following types, if anyone has some feedback or direction for me:
Line 100: type for
spi_bus
Line 125: type for
file_name
I'm happy to update the PR with the right types or fix any errors.
Thanks!