Skip to content

mypy: Add type hints - #477

Open
Daverball wants to merge 6 commits into
Pylons:mainfrom
Daverball:mypy
Open

mypy: Add type hints#477
Daverball wants to merge 6 commits into
Pylons:mainfrom
Daverball:mypy

Conversation

@Daverball

Copy link
Copy Markdown

Closes: #474

My curiosity got the better of me. So this is the stubs I contributed to typeshed automatically merged into the source using merge_pyi.

Afterwards I cleaned up any mistakes and completed the type hints for the internal API. I added a couple of type tests, but we could definitely add more.

I tried to keep changes to the actual code to a bare minimum. I did however change a couple of minor things. Like remove an unnecessary check for Python 2.7

You can do with these what you want, the diff is fairly large, so it will take some time to review.

@jvanasco

Copy link
Copy Markdown
Contributor

I think found a typo in your stubs-

You define a Protocol here: https://github.com/python/typeshed/blob/main/stubs/WebOb/webob/cookies.pyi#L29-L31

class _Serializer(Protocol):
    def loads(self, appstruct: Any, /) -> bytes: ...
    def dumps(self, bstruct: bytes, /) -> Any: ...

I think that should read:

class _Serializer(Protocol):
    def dumps(self, appstruct: Any, /) -> bytes: ...
    def loads(self, bstruct: bytes, /) -> Any: ...

It's basically just swapping the function names.

Notice how the input/output are reversed of the other functions: https://github.com/python/typeshed/blob/main/stubs/WebOb/webob/cookies.pyi#L96-L104

class JSONSerializer:
    def dumps(self, appstruct: Any) -> bytes: ...
    def loads(self, bstruct: bytes | str) -> Any: ...

class Base64Serializer:
    serializer: _Serializer
    def __init__(self, serializer: _Serializer | None = None) -> None: ...
    def dumps(self, appstruct: Any) -> bytes: ...
    def loads(self, bstruct: bytes) -> Any: ...

Note that SignedSerializer uses JSONSerializer if no serializer is supplied:

https://github.com/Pylons/webob/blob/main/src/webob/cookies.py#L705-L706

And CookieProfile uses the Base64Serializer

https://github.com/Pylons/webob/blob/main/src/webob/cookies.py#L813-L814

@Daverball

Copy link
Copy Markdown
Author

@jvanasco Good catch. Feel free to open an issue or PR on typeshed.

Comment thread src/webob/cookies.py Outdated
jvanasco added a commit to jvanasco/typeshed that referenced this pull request May 21, 2025
@digitalresistor

Copy link
Copy Markdown
Member

Following up from #457, which I closed in favour of this PR — thanks for putting this together, having the typeshed stubs merged back inline is a big step.

One gap worth flagging: this PR adds src/webob/py.typed but doesn't touch src/webob/__init__.py, which still imports six names for re-export that aren't in its __all__:

BaseRequest, parse_date, parse_date_delta,
serialize_date, serialize_date_delta, timedelta_to_seconds

Harmless today, since nothing reads our inline types. With py.typed shipped it becomes a downstream error. Installing this branch into a clean venv and checking a consumer that does from webob import BaseRequest, parse_date:

# pyright, default settings — reportPrivateImportUsage is an error at basic level
error: "BaseRequest" is not exported from module "webob"
  Import from "webob.request" instead  [reportPrivateImportUsage]

# mypy --strict
error: Module "webob" does not explicitly export attribute "BaseRequest"  [attr-defined]

All six fail the same way. Plain mypy without --strict is clean, so the blast radius is every pyright user plus strict mypy users.

The part that makes it easy to miss: our own CI won't catch it. mypy -p webob passes clean on this branch (19 files, no issues), because no_implicit_reexport flags the importing module and nothing inside WebOb imports these from the top-level package. It only surfaces for consumers, after release.

Nothing for you to do about the __init__.py side — I've handled it in #497, since it's an API decision rather than a typing one. Since 2.0 is a major, we're dropping the five date helpers from the top-level namespace outright rather than exporting them: they moved to webob.datetime_utils in 0.9.7 (0196b09) and have only been re-exported from webob for backwards compatibility since, never documented and never in __all__. BaseRequest is genuinely public, so it just gets added to __all__. The UTC tzinfo and the year/month/week/day/hour/minute/second constants are unaffected.

With #497 applied on top of this branch, the same consumer file is clean under both pyright and mypy --strict.

One thing I would like to ask of you here, though: the tests/mypy/ harness you added catches this class of bug nicely, and a permanent check there would stop top-level re-exports silently regressing again. A file containing just the public top-level imports is enough — I tried it against mypy tests/mypy/ and it failed as expected before the fix:

tests/mypy/check_reexports.py:3: error: Module "webob" does not explicitly export attribute "BaseRequest"
tests/mypy/check_reexports.py:3: error: Module "webob" does not explicitly export attribute "parse_date"

It makes more sense on this branch than on #497, which has no mypy dependency to hang it off — so I've left it out of that PR. Note it'll need BaseRequest importable from webob and the date helpers from webob.datetime_utils to match #497's end state.

Adds `BaseResponse` to `__all__` to match future expectations.

Resolves merge conflicts.
@Daverball

Daverball commented Aug 2, 2026

Copy link
Copy Markdown
Author

I think I've made all of the changes you asked for and resolved the merge conflicts.

I went ahead and already added BaseRequest to __all__ in __init__.py so the new test passes, but I left out the other changes from #497.

pull Bot pushed a commit to AKJUS/webob that referenced this pull request Aug 3, 2026
parse_date, parse_date_delta, serialize_date, serialize_date_delta and
timedelta_to_seconds moved to webob.datetime_utils in 0.9.7 (0196b09) and
have been re-exported from webob for backwards compatibility ever since.
They were never documented and never listed in webob.__all__. Import them
from webob.datetime_utils instead.

The UTC tzinfo object and the year/month/week/day/hour/minute/second
timedelta constants are unaffected and remain importable from webob.

Also add BaseRequest to webob.__all__. It was already importable from
webob, but its absence from __all__ means type checkers reject
"from webob import BaseRequest" once WebOb ships a py.typed marker, as
proposed in Pylons#477.

See Pylons#457
@Daverball

This comment was marked as resolved.

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.

Adding type hints

3 participants