Skip to content
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

Add type hints (part 1) #72

Closed
wants to merge 14 commits into from

Conversation

eltbus
Copy link
Contributor

@eltbus eltbus commented Jan 21, 2024

Partial type hinting + refactor of the cache implementation in Field

@eltbus eltbus force-pushed the refactor/add-type-hints-part-1 branch from c23b70b to 3b0eef8 Compare January 21, 2024 20:14
Copy link
Owner

@Kludex Kludex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use __future__.annotations, so we don't need to import List, and others.

Also, can we add the pyright configuration (pyproject), and do it incrementally? Maybe ignoring all rules, and incrementally removing the ignores from the config file.

Copy link
Owner

@Kludex Kludex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking long. Please ping me if I take more than 2 days.

pyproject.toml Outdated
Comment on lines 64 to 66
exclude = [
"**/__pycache__",
]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

exclude = [
"**/__pycache__",
]
reportUndefinedVariable = false # TODO: required because pyright does not work with __future__.annotations
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does work... What do you mean by this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Kludex!

What I mean by this is that Pyright will raise "false positive" undefinedVariable warnings.

➜  ~ pyright --version
pyright 1.1.349

➜  ~ cat kludex.py
from __future__ import annotations

def foo(s: str) -> List[str]: # Gonna get a Pyright complain here
    return s.split()

➜  ~ pyright kludex.py
/Users/eltbus/kludex.py
  /Users/eltbus/kludex.py:3:20 - error: "List" is not defined (reportUndefinedVariable)
1 error, 0 warnings, 0 informations

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pyright is right there... You need to use list instead of List. 🤔

List should be imported if you want to use it anyway.

Copy link
Contributor Author

@eltbus eltbus Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I believed this would not work for < Python3.9, but it indeed works.
Nonetheless pyright will also complain for Optional, Union, Callable, and others unless explicitly imported from typing.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, that's a python thing, not a Pyright... If the class is not present, and is not built-in, it needs to be imported.

Instead of Optional[T], use T | None; instead of Union[T, K], use T | K; Callable just needs to be imported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that Python3.10 syntax?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use __future__.annotations, what is on the type annotations doesn't matter for runtime.

@@ -33,10 +35,16 @@ class Base64Decoder:
:param underlying: the underlying object to pass writes to
"""

def __init__(self, underlying):
def __init__(self, underlying: IOBase):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is underlying a IOBase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a stream or file-like object with a read/write interface. From Base64Decoder's docstring:

"""
...
        from multipart.decoders import Base64Decoder
        fd = open("notb64.txt", "wb")
        decoder = Base64Decoder(fd)
...

I would appreciate more feedback on this from you though.

Comment on lines 81 to 87
@overload
def parse_options_header(value: str) -> Tuple[bytes, Dict[bytes, bytes]]: ...

@overload
def parse_options_header(value: bytes) -> Tuple[bytes, Dict[bytes, bytes]]: ...

def parse_options_header(value):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just this?

Suggested change
@overload
def parse_options_header(value: str) -> Tuple[bytes, Dict[bytes, bytes]]: ...
@overload
def parse_options_header(value: bytes) -> Tuple[bytes, Dict[bytes, bytes]]: ...
def parse_options_header(value):
def parse_options_header(value: Union[str, bytes]) -> Tuple[bytes, Dict[bytes, bytes]]:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +128 to +154
class Cache(Generic[T]):
__slots__ = ('_value', '_is_set')

def __init__(self):
self._value: Optional[T] = None
self._is_set: bool = False

@property
def value(self) -> Optional[T]:
if self.is_set:
return self._value
else:
raise ValueError("Value not yet set")

@value.setter
def value(self, v: T):
self._value = v
self._is_set = True

def clear(self):
"""Reset the cache"""
self._value = None
self._is_set = False

def is_set(self) -> bool:
"""Check if value has been set"""
return self._is_set
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we wait for this kind of change till we have a better test coverage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your call. I believe I added this to fix type-hinting for Field.__repr__ only, so it is not important.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer for this PR to not have runtime changes, unless really needed.

@Kludex Kludex marked this pull request as ready for review February 3, 2024 12:24
@Kludex
Copy link
Owner

Kludex commented Feb 10, 2024

I'll manage this. Sorry for making you lose time.

@Kludex Kludex closed this Feb 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants