Skip to content

Commit 17a8c92

Browse files
authored
Finish stubs for webob.cookies and improve samesite type (#10927)
1 parent 2ce9dcd commit 17a8c92

File tree

2 files changed

+90
-55
lines changed

2 files changed

+90
-55
lines changed

stubs/WebOb/webob/cookies.pyi

Lines changed: 88 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
from _typeshed import Incomplete
1+
from _typeshed import sentinel
22
from _typeshed.wsgi import WSGIEnvironment
3-
from collections.abc import ItemsView, Iterator, KeysView, MutableMapping, ValuesView
3+
from collections.abc import Callable, Collection, ItemsView, Iterator, KeysView, MutableMapping, ValuesView
44
from datetime import date, datetime, timedelta
5-
from typing import TypeVar, overload
6-
from typing_extensions import Literal
5+
from hashlib import _Hash
6+
from typing import Any, Protocol, TypeVar, overload
7+
from typing_extensions import Literal, TypeAlias
78

89
from webob.descriptors import _AsymmetricProperty
10+
from webob.request import Request
11+
from webob.response import Response
912

1013
_T = TypeVar("_T")
14+
# we accept both the official spelling and the one used in the WebOb docs
15+
# the implementation compares after lower() so technically there are more
16+
# valid spellings, but it seems mor natural to support these two spellings
17+
_SameSitePolicy: TypeAlias = Literal["Strict", "Lax", "None", "strict", "lax", "none"]
18+
19+
class _Serializer(Protocol):
20+
def loads(self, __appstruct: Any) -> bytes: ...
21+
def dumps(self, __bstruct: bytes) -> Any: ...
1122

1223
class RequestCookies(MutableMapping[str, str]):
1324
def __init__(self, environ: WSGIEnvironment) -> None: ...
@@ -61,7 +72,7 @@ class Morsel(dict[bytes, bytes | bool | None]):
6172
def secure(self) -> bool | None: ...
6273
@secure.setter
6374
def secure(self, v: bool) -> None: ...
64-
samesite: _AsymmetricProperty[bytes | None, Literal["strict", "lax", "none"] | None]
75+
samesite: _AsymmetricProperty[bytes | None, _SameSitePolicy | None]
6576
def serialize(self, full: bool = True) -> str: ...
6677
def __str__(self, full: bool = True) -> str: ...
6778

@@ -74,75 +85,98 @@ def make_cookie(
7485
secure: bool = False,
7586
httponly: bool = False,
7687
comment: str | None = None,
77-
samesite: Literal["strict", "lax", "none"] | None = None,
88+
samesite: _SameSitePolicy | None = None,
7889
) -> str: ...
7990

8091
class JSONSerializer:
81-
def dumps(self, appstruct): ...
82-
def loads(self, bstruct): ...
92+
def dumps(self, appstruct: Any) -> bytes: ...
93+
def loads(self, bstruct: bytes | str) -> Any: ...
8394

8495
class Base64Serializer:
85-
serializer: Incomplete
86-
def __init__(self, serializer: Incomplete | None = None) -> None: ...
87-
def dumps(self, appstruct): ...
88-
def loads(self, bstruct): ...
96+
serializer: _Serializer
97+
def __init__(self, serializer: _Serializer | None = None) -> None: ...
98+
def dumps(self, appstruct: Any) -> bytes: ...
99+
def loads(self, bstruct: bytes) -> Any: ...
89100

90101
class SignedSerializer:
91-
salt: Incomplete
92-
secret: Incomplete
93-
hashalg: Incomplete
94-
salted_secret: Incomplete
95-
digestmod: Incomplete
96-
digest_size: Incomplete
97-
serializer: Incomplete
98-
def __init__(self, secret, salt, hashalg: str = "sha512", serializer: Incomplete | None = None) -> None: ...
99-
def dumps(self, appstruct): ...
100-
def loads(self, bstruct): ...
102+
salt: str
103+
secret: str
104+
hashalg: str
105+
salted_secret: bytes
106+
digestmod: Callable[[bytes], _Hash]
107+
digest_size: int
108+
serializer: _Serializer
109+
def __init__(self, secret: str, salt: str, hashalg: str = "sha512", serializer: _Serializer | None = None) -> None: ...
110+
def dumps(self, appstruct: Any) -> bytes: ...
111+
def loads(self, bstruct: bytes) -> Any: ...
101112

102113
class CookieProfile:
103-
cookie_name: Incomplete
104-
secure: Incomplete
105-
max_age: Incomplete
106-
httponly: Incomplete
107-
samesite: Incomplete
108-
path: Incomplete
109-
domains: Incomplete
110-
serializer: Incomplete
111-
request: Incomplete
114+
cookie_name: str
115+
secure: bool
116+
max_age: int | timedelta | None
117+
httponly: bool | None
118+
samesite: _SameSitePolicy | None
119+
path: str
120+
domains: Collection[str] | None
121+
serializer: _Serializer
122+
request: Request | None
112123
def __init__(
113124
self,
114-
cookie_name,
125+
cookie_name: str,
115126
secure: bool = False,
116-
max_age: Incomplete | None = None,
117-
httponly: Incomplete | None = None,
118-
samesite: Incomplete | None = None,
127+
max_age: int | timedelta | None = None,
128+
httponly: bool | None = None,
129+
samesite: _SameSitePolicy | None = None,
119130
path: str = "/",
120-
domains: Incomplete | None = None,
121-
serializer: Incomplete | None = None,
131+
# even though the docs claim any iterable is fine, that is
132+
# clearly not the case judging by the implementation
133+
domains: Collection[str] | None = None,
134+
serializer: _Serializer | None = None,
122135
) -> None: ...
123-
def __call__(self, request): ...
124-
def bind(self, request): ...
125-
def get_value(self): ...
126-
def set_cookies(self, response, value, domains=..., max_age=..., path=..., secure=..., httponly=..., samesite=...): ...
127-
def get_headers(self, value, domains=..., max_age=..., path=..., secure=..., httponly=..., samesite=...): ...
136+
def __call__(self, request: Request) -> CookieProfile: ...
137+
def bind(self, request: Request) -> CookieProfile: ...
138+
def get_value(self) -> Any | None: ...
139+
def set_cookies(
140+
self,
141+
response: Response,
142+
value: Any,
143+
domains: Collection[str] = sentinel,
144+
max_age: int | timedelta | None = sentinel,
145+
path: str = sentinel,
146+
secure: bool = sentinel,
147+
httponly: bool = sentinel,
148+
samesite: _SameSitePolicy | None = sentinel,
149+
) -> Response: ...
150+
def get_headers(
151+
self,
152+
value: Any,
153+
domains: Collection[str] = sentinel,
154+
max_age: int | timedelta | None = sentinel,
155+
path: str = sentinel,
156+
secure: bool = sentinel,
157+
httponly: bool = sentinel,
158+
samesite: _SameSitePolicy | None = sentinel,
159+
) -> list[tuple[str, str]]: ...
128160

129161
class SignedCookieProfile(CookieProfile):
130-
secret: Incomplete
131-
salt: Incomplete
132-
hashalg: Incomplete
133-
original_serializer: Incomplete
162+
secret: str
163+
salt: str
164+
hashalg: str
165+
serializer: SignedSerializer
166+
original_serializer: _Serializer
134167
def __init__(
135168
self,
136-
secret,
137-
salt,
138-
cookie_name,
169+
secret: str,
170+
salt: str,
171+
cookie_name: str,
139172
secure: bool = False,
140-
max_age: Incomplete | None = None,
173+
max_age: int | timedelta | None = None,
141174
httponly: bool = False,
142-
samesite: Incomplete | None = None,
175+
samesite: _SameSitePolicy | None = None,
143176
path: str = "/",
144-
domains: Incomplete | None = None,
177+
domains: Collection[str] | None = None,
145178
hashalg: str = "sha512",
146-
serializer: Incomplete | None = None,
179+
serializer: _Serializer | None = None,
147180
) -> None: ...
148-
def bind(self, request): ...
181+
def __call__(self, request: Request) -> SignedCookieProfile: ...
182+
def bind(self, request: Request) -> SignedCookieProfile: ...

stubs/WebOb/webob/response.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ from typing_extensions import Literal, TypeAlias, TypedDict
77

88
from webob.byterange import ContentRange
99
from webob.cachecontrol import _ResponseCacheControl
10+
from webob.cookies import _SameSitePolicy
1011
from webob.descriptors import _AsymmetricProperty, _AsymmetricPropertyWithDelete, _authorization, _DateProperty, _ListProperty
1112
from webob.headers import ResponseHeaders
1213
from webob.request import Request
@@ -134,7 +135,7 @@ class Response:
134135
httponly: bool = False,
135136
comment: str | None = None,
136137
overwrite: bool = False,
137-
samesite: Literal["strict", "lax", "none"] | None = None,
138+
samesite: _SameSitePolicy | None = None,
138139
) -> None: ...
139140
def delete_cookie(self, name: str, path: str = "/", domain: str | None = None) -> None: ...
140141
def unset_cookie(self, name: str, strict: bool = True) -> None: ...

0 commit comments

Comments
 (0)