Conversation
| from .signer import Signer | ||
|
|
||
| if _t.TYPE_CHECKING: | ||
| _t_str_bytes = _t.Union[str, bytes] |
There was a problem hiding this comment.
Would it be better to stick commonly used types in a separate module/file?
There was a problem hiding this comment.
I thought about this, but ultimately decided it wasn't worth the further indirection.
Only a few types (really just
str_bytes) were common between modules, didn't bother with a common_typingmodule.
For a bigger library it might make more sense.
There was a problem hiding this comment.
That makes sense. I find that it helps to stick them in the same file and for all usages to reference a single definition because it encourages consistency.
teymour-aldridge
left a comment
There was a problem hiding this comment.
A note on possibly reorganising things.
Github made me write this comment to submit the review.
|
Oh, looks like that comment wasn't needed??? |
|
|
||
|
|
||
| def base64_encode(string): | ||
| def base64_encode(string: "_t_str_bytes") -> bytes: |
There was a problem hiding this comment.
This is definitely a stylistic question, but I find this tricky to read and understand. The type hints for me are much better when explicit for documentation purposes.
Also is AnyStr not appropriate for this?
There was a problem hiding this comment.
I got tired of writing the full syntax over and over again. This one is fairly simple to replace, but since I was making aliases anyway for more complex types, and this was used so much, I made it an alias too. If you think this is too distracting I can change it, although I'd want to know what the cutoff is for using an alias or not. Note that PyCharm and Mypy both report the real type, not the alias.
AnyStr is a TypeVar, so its intended use is for ensuring consistency across arguments/return. The Union alias is more "anything goes", which represents how ItsDangerous works right now. See my discussion on str and bytes.
There was a problem hiding this comment.
I feel like the types are an additional piece of documentation (they also show up to developers, mostly when using IDEs and sometimes in documentation) so it's nice if the names are meaningful.
There was a problem hiding this comment.
Ah, Sphinx does not dereference the aliases, so it does look weird there. Maybe there's a Sphinx plugin for this?
Thinking about annotating Flask.make_response, I really wouldn't want to inline the huge annotation for what it accepts, especially since that would be useful as an annotation for view functions in user code. So hopefully there's a way to make the Sphinx output clear.
|
Moving the docs discussion out of review. It seems that Sphinx autodoc relies on |
|
I think we should have the aliases (e.g. for |
|
The
|
|
I'd argue that the types are public because they show up to people using them in development. Keeping a distinction sounds good though. |
|
The typing information is public in that it's now available when a project uses a type checker, but the exact aliases we used aren't. If there was some alias that we expected users to use in their own code, then we would make it a public name. For example, the argument to |
|
Sphinx 3.0 added
|
|
Just in case, does https://github.com/agronholm/sphinx-autodoc-typehints help with anything here? |
|
I saw it but didn't try it. |
|
Since we were asking about it, I tried replacing the I'm trying to add types to the tests, but it's fairly frustrating as I do a lot of little weird things in the tests. For example, even if you annotate |
|
I think it's a bug in mypy. There's a few of them in mypy which have been around for a really long time (the inability to make typed NamedTuple's generic is another one). |
21719b3 to
fc948ac
Compare
|
I ignored the |
|
Here's an example of the docs: https://itsdangerous.palletsprojects.com/en/master/serializer/#itsdangerous.serializer.Serializer |

Implementation notes:
import typing as _tto shorten things.if TYPE_CHECKING:block and referenced as string names.str_bytes) were common between modules, didn't bother with a common_typingmodule.return_timestampparameter ofTimestampSigner.unsignchanges the return type. To distinguish these,@overloadis used, but because the method takes some other optional parameters, many overloads are needed to cover every combination. I added the overloads that matter, as mypy does use that to figure out a type elsewhere, but ignored the finding about incompatible overlap.@typing.overloaddoesn't trigger a redefinition error, but it has to be literallytyping.overload,_t.overloadisn't recognized. So had to ignore that Flake8 finding.Protocol, soSerializer.serializerhas theAnytype for now. See Allow using modules as subtypes of protocols python/mypy#5018.Findings and future work:
TimedSerializer.loadsandloads_unsafehave incompatible signatures withSerializer.loadsbecause extra parameters were added before thesaltparameter. This violates the Liskov substitution principle, and should probably be migrated with*argsand a deprecation warning at some point. I added aTODOin the code.Between removing Python 2 compat helpers and adding typing, I'm more convinced that accepting
bytesandstrinterchangeably everywhere is not good. Python 3 emphasizes understanding the boundary between the two.Because pretty much every single point in the ItsDangerous API accepts either,
want_bytesis called over and over again, even where it's redundant because an earlier function already called it. I already movedwants_bytesaround to get a few spots that were missed. This isn't a huge deal in ItsDangerous, but it's probably hurting performance in Werkzeug where it happens much more often.It's still probably useful to accept both as the data passed to
Serializer.loads,Signer.sign, andSinger.unsign, since you might be signing either bytes or text, and received data to be loaded might be bytes or text (ASGI vs WSGI, for example). Everything else should probably be bytes only since that's how they're used.cc @pgjones