-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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 typings for Payload #3294
Add typings for Payload #3294
Conversation
@@ -107,27 +126,27 @@ def __init__(self, value, *, headers=None, content_type=sentinel, | |||
self._content_type = content_type | |||
|
|||
@property | |||
def size(self): | |||
def size(self) -> Optional[float]: |
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.
All those Optional
seem a little awkward, but honestly speaking that's how I read the logic. Might as well be that I got that wrong.
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.
It is a pretty common case.
For example AsyncIterablePayload
has no size but sends a payload chunk-by-chunk
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.
@asvetlov this is very first attempt to do that. But I guess I am lacking some knowledge here, so any suggestion appreciated.
Firstly, let's maybe concentrate on:
aiohttp/payload.py:73: error: Incompatible default for argument "_CHAIN" (default has type "Type[chain[Any]]", argument has type "Callable[..., Tuple[Type[Payload], Any]]")
aiohttp/payload.py:167: error: Unsupported target for indexed assignment
aiohttp/payload.py:191: error: Too many arguments for "__init__" of "Payload"
aiohttp/payload.py:260: error: Too many arguments for "__init__" of "Payload"
aiohttp/payload.py:381: error: Too many arguments for "__init__" of "Payload"
I will try to dig up on my own regarding too many arguments. But I am all puzzled about the first error in the list. Have no idea how am I suppose to make that passing :(
Also, not sure if the typings all all correct for the payloads that have IO
in the name.
And yeah, sorry that it goes like that. Just trying to play open cards ;-)
Thanks for the PR! |
Since |
aiohttp/payload.py
Outdated
def __init__(self, value, *, headers=None, content_type=sentinel, | ||
filename=None, encoding=None, **kwargs): | ||
_size = None # type: Optional[float] | ||
_headers = None # type: Optional[LooseHeaders] |
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.
_headers
has the concrete type Optional[CIMultiDict[str]]
.
LooseHeaders
is used for passing any mapping-like type.
aiohttp client API converts LooseHeaders
into CIMultiDict
internally.
I'm not 100% sure but a payload should accept CIMultiDict
objects only.
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.
aiohttp/payload.py
Outdated
def get(self, | ||
data: Any, | ||
*args: Any, | ||
_CHAIN: Callable[..., Tuple[Type['Payload'], Any]]=chain, |
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.
Use _CHAIN: Any
here, it's ok to shut up the type checker for the argument.
Ok, so mypy no longer complains, but I am getting NVM, found that. Removed one extra |
Codecov Report
@@ Coverage Diff @@
## master #3294 +/- ##
==========================================
- Coverage 98.03% 98.02% -0.01%
==========================================
Files 43 43
Lines 8001 8018 +17
Branches 1354 1355 +1
==========================================
+ Hits 7844 7860 +16
Misses 65 65
- Partials 92 93 +1
Continue to review full report at Codecov.
|
aiohttp/typedefs.py
Outdated
JSONEncoder = Callable[[Any], str] | ||
JSONDecoder = Callable[[str], Any] | ||
JSONObj = Dict[str, Any] | ||
JSON = Union[ |
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.
Technically JSON can contain a single integer, None
or string.
Moreover, it can be any python object if there is python -> JSON converter passed by json.dums(obj, defaul=my_converter)
.
Let's use Any
for json encoders/decoders.
aiohttp/payload.py
Outdated
super().__init__( | ||
value.encode(encoding), | ||
encoding=encoding, content_type=content_type, *args, **kwargs) | ||
if encoding is 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.
Ok, this bit I completely do not get. It's literally impossible for encoding to be None, based on the logic. But the typing says otherwise and we can't exactly redefine the value with mypy
. We can't also do the
_encoding: str
_content_type: str
letting the assignment to come later, because tests on 3.5
complains. We could've do the typing.cast
but not really sure if that's correct. Sounds a bit hacky to me.
@asvetlov any suggestions ?
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.
Alternatively, we can keep that raise ValueError
and add some noqa
there, but honestly that sounds bad as well to me.
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.
Oh, and for the sake of discussion, this is the alternative:
class StringPayload(BytesPayload):
def __init__(self,
value: str,
*args: Any,
encoding: Optional[str]=None,
content_type: Optional[str]=None,
**kwargs: Any) -> None:
_encoding, _content_type = self._parse_args(encoding, content_type)
super().__init__(
value.encode(_encoding),
encoding=_encoding,
content_type=_content_type,
*args,
**kwargs)
@staticmethod
def _parse_args(
encoding: Optional[str]=None,
content_type: Optional[str]=None,
) -> Tuple[str, str]:
if encoding and content_type:
return encoding, content_type
elif encoding is None and content_type is None:
return 'utf-8', 'text/plain; charset=utf-8'
elif encoding is None and content_type:
mimetype = parse_mimetype(content_type)
return (
mimetype.parameters.get('charset', 'utf-8'),
content_type,
)
else:
return (
cast(str, encoding),
'text/plain; charset=%s' % cast(str, encoding),
)
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.
Revert back the change, drop if encoding is None
check.
Use the following trick:
if encoding is None:
if content_type is None:
real_encoding = 'utf-8'
content_type = 'text/plain; charset=utf-8'
else:
mimetype = parse_mimetype(content_type)
readl_encoding = mimetype.parameters.get('charset', 'utf-8')
else:
if content_type is None:
content_type = 'text/plain; charset=%s' % encoding
real_encoding = encoding
super().__init__(
value.encode(encoding),
encoding=real_encoding,
content_type=content_type,
*args,
**kwargs)
real_encoding
variable has strict string type, not Optional[str]
.
I believe mypy understands it pretty well.
aiohttp/payload.py
Outdated
|
||
super().__init__( | ||
dumps(value).encode(encoding), | ||
content_type=content_type, encoding=encoding, *args, **kwargs) | ||
|
||
|
||
if TYPE_CHECKING: |
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.
Please add # pragma: no cover
comment to the line for hinting coverage tool
Please use another approach. |
Doesnt it mean that _CIMultiDict should be removed and replaced with forward references ? I mean at all...not as a sugestiom for me. Given your comment I got the feeling I used sth I should not have 😎 |
Thanks! |
Ok, working currently on |
|
|
||
def __init__(self, value, *, headers=None, content_type=sentinel, | ||
filename=None, encoding=None, **kwargs): | ||
_size = None # type: Optional[float] |
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.
A bit late for the party, but why size is typed as float? This value is used for Content-Length header which defined number of bytes of payload size - it's hard to pass over HTTP some fraction of a byte. What was the case for float type?
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.
What do these changes do?
Adds typing for the
aiohttp/payload.py
Are there changes in behavior for the user?
No, these are just hints for the type checkers.
Related issue number
#1749
Checklist
CONTRIBUTORS.txt
CHANGES
folder<issue_id>.<type>
for example (588.bugfix)issue_id
change it to the pr id after creating the pr.feature
: Signifying a new feature..bugfix
: Signifying a bug fix..doc
: Signifying a documentation improvement..removal
: Signifying a deprecation or removal of public API..misc
: A ticket has been closed, but it is not of interest to users.