fresco 3.5.0__py3-none-any.whl → 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fresco might be problematic. Click here for more details.
- fresco/__init__.py +55 -56
- fresco/core.py +33 -23
- fresco/decorators.py +6 -3
- fresco/defaults.py +1 -0
- fresco/middleware.py +4 -4
- fresco/multidict.py +35 -51
- fresco/options.py +146 -75
- fresco/request.py +155 -34
- fresco/requestcontext.py +3 -0
- fresco/response.py +12 -9
- fresco/routeargs.py +23 -9
- fresco/routing.py +74 -56
- fresco/static.py +1 -1
- fresco/subrequests.py +1 -1
- fresco/tests/test_core.py +4 -4
- fresco/tests/test_multidict.py +2 -2
- fresco/tests/test_options.py +40 -16
- fresco/tests/test_request.py +21 -10
- fresco/tests/test_routing.py +36 -33
- fresco/tests/util/test_http.py +1 -3
- fresco/types.py +28 -2
- fresco/util/cache.py +2 -1
- fresco/util/http.py +66 -46
- fresco/util/urls.py +13 -11
- fresco/util/wsgi.py +15 -14
- {fresco-3.5.0.dist-info → fresco-3.6.0.dist-info}/METADATA +3 -2
- {fresco-3.5.0.dist-info → fresco-3.6.0.dist-info}/RECORD +30 -30
- {fresco-3.5.0.dist-info → fresco-3.6.0.dist-info}/WHEEL +1 -1
- fresco/typing.py +0 -17
- {fresco-3.5.0.dist-info → fresco-3.6.0.dist-info/licenses}/LICENSE.txt +0 -0
- {fresco-3.5.0.dist-info → fresco-3.6.0.dist-info}/top_level.txt +0 -0
fresco/__init__.py
CHANGED
|
@@ -12,10 +12,63 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
|
-
|
|
15
|
+
from fresco.request import Request
|
|
16
|
+
from fresco.request import currentrequest
|
|
17
|
+
from fresco.requestcontext import context
|
|
18
|
+
from fresco.response import Response
|
|
19
|
+
from fresco.core import FrescoApp
|
|
20
|
+
from fresco.core import urlfor
|
|
21
|
+
from fresco.defaults import DEFAULT_CHARSET
|
|
22
|
+
from fresco.options import Options
|
|
23
|
+
from fresco.routing import Route
|
|
24
|
+
from fresco.routing import RRoute
|
|
25
|
+
from fresco.routing import RouteCollection
|
|
26
|
+
from fresco.routing import DelegateRoute
|
|
27
|
+
from fresco.routing import routefor
|
|
28
|
+
from fresco.routing import ALL_METHODS
|
|
29
|
+
from fresco.routing import GET
|
|
30
|
+
from fresco.routing import HEAD
|
|
31
|
+
from fresco.routing import POST
|
|
32
|
+
from fresco.routing import PUT
|
|
33
|
+
from fresco.routing import DELETE
|
|
34
|
+
from fresco.routing import OPTIONS
|
|
35
|
+
from fresco.routing import TRACE
|
|
36
|
+
from fresco.routing import CONNECT
|
|
37
|
+
from fresco.routing import VERSION_CONTROL
|
|
38
|
+
from fresco.routing import REPORT
|
|
39
|
+
from fresco.routing import CHECKOUT
|
|
40
|
+
from fresco.routing import CHECKIN
|
|
41
|
+
from fresco.routing import UNCHECKOUT
|
|
42
|
+
from fresco.routing import MKWORKSPACE
|
|
43
|
+
from fresco.routing import UPDATE
|
|
44
|
+
from fresco.routing import LABEL
|
|
45
|
+
from fresco.routing import MERGE
|
|
46
|
+
from fresco.routing import BASELINE_CONTROL
|
|
47
|
+
from fresco.routing import MKACTIVITY
|
|
48
|
+
from fresco.routing import ORDERPATCH
|
|
49
|
+
from fresco.routing import ACL
|
|
50
|
+
from fresco.routing import SEARCH
|
|
51
|
+
from fresco.routing import PATCH
|
|
52
|
+
from fresco.routeargs import routearg
|
|
53
|
+
from fresco.routeargs import FormArg
|
|
54
|
+
from fresco.routeargs import PostArg
|
|
55
|
+
from fresco.routeargs import QueryArg
|
|
56
|
+
from fresco.routeargs import GetArg
|
|
57
|
+
from fresco.routeargs import CookieArg
|
|
58
|
+
from fresco.routeargs import SessionArg
|
|
59
|
+
from fresco.routeargs import RequestObject
|
|
60
|
+
from fresco.routeargs import FormData
|
|
61
|
+
from fresco.routeargs import PostData
|
|
62
|
+
from fresco.routeargs import QueryData
|
|
63
|
+
from fresco.routeargs import GetData
|
|
64
|
+
from fresco.middleware import XForwarded
|
|
65
|
+
from fresco.subrequests import subrequest
|
|
66
|
+
from fresco.subrequests import subrequest_bytes
|
|
67
|
+
from fresco.subrequests import subrequest_raw
|
|
68
|
+
from fresco.util.common import object_or_404
|
|
16
69
|
|
|
17
|
-
DEFAULT_CHARSET = "UTF-8"
|
|
18
70
|
|
|
71
|
+
__version__ = "3.6.0"
|
|
19
72
|
__all__ = [
|
|
20
73
|
"Request",
|
|
21
74
|
"currentrequest",
|
|
@@ -72,57 +125,3 @@ __all__ = [
|
|
|
72
125
|
"subrequest_raw",
|
|
73
126
|
"object_or_404",
|
|
74
127
|
]
|
|
75
|
-
|
|
76
|
-
from fresco.request import Request
|
|
77
|
-
from fresco.request import currentrequest
|
|
78
|
-
from fresco.requestcontext import context
|
|
79
|
-
from fresco.response import Response
|
|
80
|
-
from fresco.core import FrescoApp
|
|
81
|
-
from fresco.core import urlfor
|
|
82
|
-
from fresco.options import Options
|
|
83
|
-
from fresco.routing import Route
|
|
84
|
-
from fresco.routing import RRoute
|
|
85
|
-
from fresco.routing import RouteCollection
|
|
86
|
-
from fresco.routing import DelegateRoute
|
|
87
|
-
from fresco.routing import routefor
|
|
88
|
-
from fresco.routing import ALL_METHODS
|
|
89
|
-
from fresco.routing import GET
|
|
90
|
-
from fresco.routing import HEAD
|
|
91
|
-
from fresco.routing import POST
|
|
92
|
-
from fresco.routing import PUT
|
|
93
|
-
from fresco.routing import DELETE
|
|
94
|
-
from fresco.routing import OPTIONS
|
|
95
|
-
from fresco.routing import TRACE
|
|
96
|
-
from fresco.routing import CONNECT
|
|
97
|
-
from fresco.routing import VERSION_CONTROL
|
|
98
|
-
from fresco.routing import REPORT
|
|
99
|
-
from fresco.routing import CHECKOUT
|
|
100
|
-
from fresco.routing import CHECKIN
|
|
101
|
-
from fresco.routing import UNCHECKOUT
|
|
102
|
-
from fresco.routing import MKWORKSPACE
|
|
103
|
-
from fresco.routing import UPDATE
|
|
104
|
-
from fresco.routing import LABEL
|
|
105
|
-
from fresco.routing import MERGE
|
|
106
|
-
from fresco.routing import BASELINE_CONTROL
|
|
107
|
-
from fresco.routing import MKACTIVITY
|
|
108
|
-
from fresco.routing import ORDERPATCH
|
|
109
|
-
from fresco.routing import ACL
|
|
110
|
-
from fresco.routing import SEARCH
|
|
111
|
-
from fresco.routing import PATCH
|
|
112
|
-
from fresco.routeargs import routearg
|
|
113
|
-
from fresco.routeargs import FormArg
|
|
114
|
-
from fresco.routeargs import PostArg
|
|
115
|
-
from fresco.routeargs import QueryArg
|
|
116
|
-
from fresco.routeargs import GetArg
|
|
117
|
-
from fresco.routeargs import CookieArg
|
|
118
|
-
from fresco.routeargs import SessionArg
|
|
119
|
-
from fresco.routeargs import RequestObject
|
|
120
|
-
from fresco.routeargs import FormData
|
|
121
|
-
from fresco.routeargs import PostData
|
|
122
|
-
from fresco.routeargs import QueryData
|
|
123
|
-
from fresco.routeargs import GetData
|
|
124
|
-
from fresco.middleware import XForwarded
|
|
125
|
-
from fresco.subrequests import subrequest
|
|
126
|
-
from fresco.subrequests import subrequest_bytes
|
|
127
|
-
from fresco.subrequests import subrequest_raw
|
|
128
|
-
from fresco.util.common import object_or_404
|
fresco/core.py
CHANGED
|
@@ -33,7 +33,10 @@ from fresco.util.http import encode_multipart
|
|
|
33
33
|
from fresco.util.urls import normpath, make_query
|
|
34
34
|
from fresco.util.common import fq_path
|
|
35
35
|
from fresco.util.wsgi import make_environ
|
|
36
|
-
from fresco.
|
|
36
|
+
from fresco.types import WSGIApplication
|
|
37
|
+
from fresco.types import HeaderList
|
|
38
|
+
from fresco.types import OptionalExcInfo
|
|
39
|
+
from fresco.types import WriteCallable
|
|
37
40
|
|
|
38
41
|
from fresco.exceptions import ResponseException
|
|
39
42
|
from fresco.requestcontext import context
|
|
@@ -50,6 +53,8 @@ __all__ = ("FrescoApp", "urlfor", "context")
|
|
|
50
53
|
|
|
51
54
|
logger = logging.getLogger(__name__)
|
|
52
55
|
|
|
56
|
+
ExcInfo = tuple[t.Type[BaseException], BaseException, types.TracebackType]
|
|
57
|
+
|
|
53
58
|
|
|
54
59
|
class FrescoApp(RouteCollection):
|
|
55
60
|
"""\
|
|
@@ -115,7 +120,9 @@ class FrescoApp(RouteCollection):
|
|
|
115
120
|
#: If a function returns a value (other than ``None``),
|
|
116
121
|
#: this value will be
|
|
117
122
|
#: returned as the response instead of calling the scheduled view.
|
|
118
|
-
self.process_http_error_response_handlers:
|
|
123
|
+
self.process_http_error_response_handlers: list[
|
|
124
|
+
tuple[t.Optional[int], Callable[[Request, Response], t.Optional[Response]]]
|
|
125
|
+
] = []
|
|
119
126
|
|
|
120
127
|
#: Functions to be called if an exception is raised during a view
|
|
121
128
|
#: Each function will be passed ``request, exc_info``.
|
|
@@ -123,7 +130,9 @@ class FrescoApp(RouteCollection):
|
|
|
123
130
|
#: this value will be
|
|
124
131
|
#: returned as the response and the error will not be propagated.
|
|
125
132
|
#: If all exception handlers return None then the error will be raised
|
|
126
|
-
self.process_exception_handlers:
|
|
133
|
+
self.process_exception_handlers: list[
|
|
134
|
+
tuple[Type[Exception], Callable[[Request, ExcInfo], Union[Response, None]]]
|
|
135
|
+
] = []
|
|
127
136
|
|
|
128
137
|
#: Functions to be called at the end of request processing,
|
|
129
138
|
#: after all content has been output.
|
|
@@ -156,7 +165,7 @@ class FrescoApp(RouteCollection):
|
|
|
156
165
|
method: str,
|
|
157
166
|
currentcontext=context.currentcontext,
|
|
158
167
|
normpath=normpath,
|
|
159
|
-
):
|
|
168
|
+
) -> Response:
|
|
160
169
|
ctx = currentcontext()
|
|
161
170
|
ctx["app"] = self
|
|
162
171
|
environ = request.environ
|
|
@@ -178,8 +187,8 @@ class FrescoApp(RouteCollection):
|
|
|
178
187
|
|
|
179
188
|
try:
|
|
180
189
|
for traversal in self.get_route_traversals(path, method, request):
|
|
190
|
+
route = traversal.route
|
|
181
191
|
try:
|
|
182
|
-
route = traversal.route
|
|
183
192
|
environ["wsgiorg.routing_args"] = (
|
|
184
193
|
traversal.args,
|
|
185
194
|
traversal.kwargs,
|
|
@@ -324,12 +333,7 @@ class FrescoApp(RouteCollection):
|
|
|
324
333
|
exc_info=exc_info,
|
|
325
334
|
)
|
|
326
335
|
|
|
327
|
-
def handle_exception(
|
|
328
|
-
self, request, allow_reraise=True
|
|
329
|
-
) -> Union[
|
|
330
|
-
Response,
|
|
331
|
-
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
|
332
|
-
]:
|
|
336
|
+
def handle_exception(self, request, allow_reraise=True) -> Response:
|
|
333
337
|
exc_info = sys.exc_info()
|
|
334
338
|
if exc_info[0] is None:
|
|
335
339
|
raise AssertionError(
|
|
@@ -346,10 +350,7 @@ class FrescoApp(RouteCollection):
|
|
|
346
350
|
# server handle it
|
|
347
351
|
if allow_reraise and not have_error_handlers:
|
|
348
352
|
raise exc_info[1].with_traceback(exc_info[2]) # type: ignore
|
|
349
|
-
response:
|
|
350
|
-
Response,
|
|
351
|
-
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
|
352
|
-
] = Response.internal_server_error()
|
|
353
|
+
response: Response = Response.internal_server_error()
|
|
353
354
|
|
|
354
355
|
if not self.process_exception_handlers:
|
|
355
356
|
self.log_exception(request, exc_info)
|
|
@@ -406,7 +407,7 @@ class FrescoApp(RouteCollection):
|
|
|
406
407
|
self.reset_wsgi_app()
|
|
407
408
|
self._middleware.insert(position, (middleware, args, kwargs))
|
|
408
409
|
|
|
409
|
-
def make_wsgi_app(self, wsgi_app=None, use_middleware=True) ->
|
|
410
|
+
def make_wsgi_app(self, wsgi_app=None, use_middleware=True) -> WSGIApplication:
|
|
410
411
|
"""
|
|
411
412
|
Return a WSGI (PEP-3333) compliant application that drives this
|
|
412
413
|
FrescoApp object.
|
|
@@ -419,7 +420,7 @@ class FrescoApp(RouteCollection):
|
|
|
419
420
|
"""
|
|
420
421
|
if wsgi_app is None:
|
|
421
422
|
|
|
422
|
-
def
|
|
423
|
+
def _wsgi_app(
|
|
423
424
|
environ,
|
|
424
425
|
start_response,
|
|
425
426
|
view=self.view,
|
|
@@ -428,15 +429,18 @@ class FrescoApp(RouteCollection):
|
|
|
428
429
|
request = request_class(environ)
|
|
429
430
|
return view(request)(environ, start_response)
|
|
430
431
|
|
|
432
|
+
else:
|
|
433
|
+
_wsgi_app = wsgi_app
|
|
434
|
+
|
|
431
435
|
if use_middleware:
|
|
432
436
|
for m, m_args, m_kwargs in self._middleware:
|
|
433
|
-
|
|
437
|
+
_wsgi_app = m(_wsgi_app, *m_args, **m_kwargs)
|
|
434
438
|
|
|
435
439
|
def fresco_wsgi_app(
|
|
436
440
|
environ,
|
|
437
441
|
start_response,
|
|
438
442
|
frescoapp=self,
|
|
439
|
-
wsgi_app=
|
|
443
|
+
wsgi_app=_wsgi_app,
|
|
440
444
|
request_class=self.request_class,
|
|
441
445
|
process_teardown_handlers=self.process_teardown_handlers,
|
|
442
446
|
call_process_teardown_handlers=self.call_process_teardown_handlers,
|
|
@@ -602,7 +606,11 @@ class FrescoApp(RouteCollection):
|
|
|
602
606
|
self.process_response_handlers.append(func)
|
|
603
607
|
return func
|
|
604
608
|
|
|
605
|
-
def process_exception(
|
|
609
|
+
def process_exception(
|
|
610
|
+
self,
|
|
611
|
+
func: Callable[[Request, ExcInfo], Union[Response, None]],
|
|
612
|
+
exc_type: Type[Exception] = Exception,
|
|
613
|
+
):
|
|
606
614
|
"""
|
|
607
615
|
Register a ``process_exception`` hook function
|
|
608
616
|
"""
|
|
@@ -668,7 +676,9 @@ class FrescoApp(RouteCollection):
|
|
|
668
676
|
start_response("200 OK", [])
|
|
669
677
|
yield b""
|
|
670
678
|
|
|
671
|
-
def fake_start_response(
|
|
679
|
+
def fake_start_response(
|
|
680
|
+
status: str, headers: HeaderList, exc_info: OptionalExcInfo = None
|
|
681
|
+
) -> WriteCallable:
|
|
672
682
|
return lambda s: None
|
|
673
683
|
|
|
674
684
|
environ = make_environ(url, environ, wsgi_input, **kwargs)
|
|
@@ -693,12 +703,12 @@ class FrescoApp(RouteCollection):
|
|
|
693
703
|
if multipart:
|
|
694
704
|
wsgi_input, headers = encode_multipart(data, files)
|
|
695
705
|
kwargs.update(headers)
|
|
696
|
-
elif
|
|
706
|
+
elif isinstance(data, t.BinaryIO):
|
|
697
707
|
wsgi_input = data.read()
|
|
698
708
|
elif isinstance(data, bytes):
|
|
699
709
|
wsgi_input = data
|
|
700
710
|
elif data is None:
|
|
701
|
-
wsgi_input = ""
|
|
711
|
+
wsgi_input = b""
|
|
702
712
|
else:
|
|
703
713
|
wsgi_input = make_query(data).encode("ascii")
|
|
704
714
|
|
fresco/decorators.py
CHANGED
|
@@ -14,20 +14,23 @@
|
|
|
14
14
|
#
|
|
15
15
|
import sys
|
|
16
16
|
from functools import wraps
|
|
17
|
+
import typing as t
|
|
17
18
|
|
|
18
19
|
from fresco import Response
|
|
19
20
|
|
|
20
21
|
_marker = object()
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
def onerror(
|
|
24
|
-
|
|
24
|
+
def onerror(
|
|
25
|
+
exceptions: t.Union[t.Type[Exception], tuple[t.Type[Exception], ...]], handler
|
|
26
|
+
) -> t.Callable:
|
|
27
|
+
"""
|
|
25
28
|
Return a decorator that can replace or update the return value of the
|
|
26
29
|
function if an exception is raised
|
|
27
30
|
"""
|
|
28
31
|
|
|
29
32
|
try:
|
|
30
|
-
if isinstance(exceptions,
|
|
33
|
+
if isinstance(exceptions, type):
|
|
31
34
|
exceptions = (exceptions,)
|
|
32
35
|
except TypeError:
|
|
33
36
|
pass
|
fresco/defaults.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT_CHARSET = "UTF-8"
|
fresco/middleware.py
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
#
|
|
15
15
|
import typing as t
|
|
16
16
|
|
|
17
|
-
from fresco.
|
|
18
|
-
from fresco.
|
|
19
|
-
from fresco.
|
|
17
|
+
from fresco.types import WSGIApplication
|
|
18
|
+
from fresco.types import WSGIEnviron
|
|
19
|
+
from fresco.types import StartResponse
|
|
20
20
|
|
|
21
21
|
__all__ = ["XForwarded"]
|
|
22
22
|
|
|
@@ -83,7 +83,7 @@ class XForwarded(object):
|
|
|
83
83
|
|
|
84
84
|
def __init__(
|
|
85
85
|
self,
|
|
86
|
-
app:
|
|
86
|
+
app: WSGIApplication,
|
|
87
87
|
trusted: t.Optional[t.Iterable[str]] = None,
|
|
88
88
|
force_https: t.Optional[bool] = None,
|
|
89
89
|
) -> None:
|
fresco/multidict.py
CHANGED
|
@@ -15,17 +15,19 @@
|
|
|
15
15
|
"""
|
|
16
16
|
An order preserving multidict implementation
|
|
17
17
|
"""
|
|
18
|
-
from collections.abc import MutableMapping
|
|
19
18
|
from collections.abc import Mapping
|
|
19
|
+
from collections.abc import KeysView
|
|
20
|
+
from collections.abc import ItemsView
|
|
21
|
+
from collections.abc import ValuesView
|
|
22
|
+
from collections.abc import MutableMapping
|
|
20
23
|
from itertools import repeat
|
|
21
24
|
import typing as t
|
|
22
25
|
from typing import Any
|
|
23
26
|
from typing import Dict
|
|
24
|
-
from typing import Set
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
if t.TYPE_CHECKING:
|
|
28
|
-
from typeshed import SupportsKeysAndGetItem
|
|
30
|
+
from typeshed import SupportsKeysAndGetItem # type: ignore
|
|
29
31
|
else:
|
|
30
32
|
SupportsKeysAndGetItem = Mapping
|
|
31
33
|
|
|
@@ -33,6 +35,8 @@ SupportsKeysAndGetItemOrIterable = t.Union[
|
|
|
33
35
|
SupportsKeysAndGetItem, t.Iterable[t.Tuple[Any, Any]]
|
|
34
36
|
]
|
|
35
37
|
|
|
38
|
+
_NO_DEFAULT = object()
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
class MultiDict(MutableMapping):
|
|
38
42
|
"""
|
|
@@ -58,11 +62,11 @@ class MultiDict(MutableMapping):
|
|
|
58
62
|
|
|
59
63
|
_dict: Dict[Any, Any]
|
|
60
64
|
_order: t.List[Any]
|
|
65
|
+
__slots__ = ("_dict", "_order")
|
|
66
|
+
|
|
61
67
|
setdefault = MutableMapping.setdefault
|
|
62
68
|
|
|
63
|
-
def __init__(
|
|
64
|
-
self, mapping_or_iterable: SupportsKeysAndGetItemOrIterable = tuple(), **kwargs
|
|
65
|
-
):
|
|
69
|
+
def __init__(self, other: SupportsKeysAndGetItemOrIterable = tuple(), **kwargs):
|
|
66
70
|
"""
|
|
67
71
|
MultiDicts can be constructed in the following ways:
|
|
68
72
|
|
|
@@ -94,7 +98,7 @@ class MultiDict(MutableMapping):
|
|
|
94
98
|
"""
|
|
95
99
|
self._order = []
|
|
96
100
|
self._dict = {}
|
|
97
|
-
self._update(
|
|
101
|
+
self._update(other, True, kwargs)
|
|
98
102
|
|
|
99
103
|
def __getitem__(self, key):
|
|
100
104
|
"""
|
|
@@ -134,7 +138,7 @@ class MultiDict(MutableMapping):
|
|
|
134
138
|
"""
|
|
135
139
|
Return an iterator over all keys
|
|
136
140
|
"""
|
|
137
|
-
return (
|
|
141
|
+
return iter(self._dict)
|
|
138
142
|
|
|
139
143
|
def get(self, key, default=None):
|
|
140
144
|
"""
|
|
@@ -155,7 +159,7 @@ class MultiDict(MutableMapping):
|
|
|
155
159
|
"""
|
|
156
160
|
return self._dict.get(key, [])
|
|
157
161
|
|
|
158
|
-
def copy(self):
|
|
162
|
+
def copy(self) -> "MultiDict":
|
|
159
163
|
"""\
|
|
160
164
|
Return a shallow copy of the dictionary:
|
|
161
165
|
|
|
@@ -169,7 +173,7 @@ class MultiDict(MutableMapping):
|
|
|
169
173
|
return self.__class__(self)
|
|
170
174
|
|
|
171
175
|
@classmethod
|
|
172
|
-
def fromkeys(cls, seq, value=None):
|
|
176
|
+
def fromkeys(cls, seq, value=None) -> "MultiDict":
|
|
173
177
|
"""\
|
|
174
178
|
Create a new MultiDict with keys from seq and values set to value.
|
|
175
179
|
|
|
@@ -189,8 +193,8 @@ class MultiDict(MutableMapping):
|
|
|
189
193
|
"""
|
|
190
194
|
return cls(zip(seq, repeat(value)))
|
|
191
195
|
|
|
192
|
-
def items(self):
|
|
193
|
-
"""
|
|
196
|
+
def items(self) -> ItemsView:
|
|
197
|
+
"""
|
|
194
198
|
Return a list of ``(key, value)`` tuples. Only the first ``(key,
|
|
195
199
|
value)`` is returned where keys have multiple values:
|
|
196
200
|
|
|
@@ -198,14 +202,9 @@ class MultiDict(MutableMapping):
|
|
|
198
202
|
>>> list(d.items())
|
|
199
203
|
[('a', 1), ('b', 3)]
|
|
200
204
|
"""
|
|
201
|
-
|
|
202
|
-
for k, v in self._order:
|
|
203
|
-
if k in seen:
|
|
204
|
-
continue
|
|
205
|
-
yield k, v
|
|
206
|
-
seen.add(k)
|
|
205
|
+
return {k: v[0] for k, v in self._dict.items()}.items()
|
|
207
206
|
|
|
208
|
-
def listitems(self):
|
|
207
|
+
def listitems(self) -> ItemsView:
|
|
209
208
|
"""\
|
|
210
209
|
Like ``items``, but returns lists of values:
|
|
211
210
|
|
|
@@ -213,10 +212,9 @@ class MultiDict(MutableMapping):
|
|
|
213
212
|
>>> list(d.listitems())
|
|
214
213
|
[('a', [1, 2]), ('b', [3])]
|
|
215
214
|
"""
|
|
216
|
-
|
|
217
|
-
yield k, self._dict[k]
|
|
215
|
+
return self._dict.items()
|
|
218
216
|
|
|
219
|
-
def allitems(self):
|
|
217
|
+
def allitems(self) -> t.Iterable[tuple[t.Any, t.Any]]:
|
|
220
218
|
"""\
|
|
221
219
|
Return ``(key, value)`` pairs for each item in the MultiDict.
|
|
222
220
|
Items with multiple keys will have multiple key-value pairs returned:
|
|
@@ -228,7 +226,7 @@ class MultiDict(MutableMapping):
|
|
|
228
226
|
"""
|
|
229
227
|
return iter(self._order)
|
|
230
228
|
|
|
231
|
-
def keys(self):
|
|
229
|
+
def keys(self) -> KeysView:
|
|
232
230
|
"""\
|
|
233
231
|
Return dictionary keys. Each key is returned only once, even if
|
|
234
232
|
multiple values are present.
|
|
@@ -238,10 +236,10 @@ class MultiDict(MutableMapping):
|
|
|
238
236
|
>>> list(d.keys())
|
|
239
237
|
['a', 'b']
|
|
240
238
|
"""
|
|
241
|
-
return
|
|
239
|
+
return self._dict.keys()
|
|
242
240
|
|
|
243
|
-
def values(self):
|
|
244
|
-
"""
|
|
241
|
+
def values(self) -> ValuesView:
|
|
242
|
+
"""
|
|
245
243
|
Return values from the dictionary. Where keys have multiple values,
|
|
246
244
|
only the first is returned:
|
|
247
245
|
|
|
@@ -250,9 +248,9 @@ class MultiDict(MutableMapping):
|
|
|
250
248
|
>>> list(d.values())
|
|
251
249
|
[1, 3]
|
|
252
250
|
"""
|
|
253
|
-
return
|
|
251
|
+
return {k: v[0] for k, v in self._dict.items()}.values()
|
|
254
252
|
|
|
255
|
-
def listvalues(self):
|
|
253
|
+
def listvalues(self) -> ValuesView:
|
|
256
254
|
"""\
|
|
257
255
|
Return an iterator over lists of values. Each item will be the list of
|
|
258
256
|
values associated with a single key.
|
|
@@ -264,9 +262,9 @@ class MultiDict(MutableMapping):
|
|
|
264
262
|
>>> list(d.listvalues())
|
|
265
263
|
[[1, 2], [3]]
|
|
266
264
|
"""
|
|
267
|
-
return
|
|
265
|
+
return self._dict.values()
|
|
268
266
|
|
|
269
|
-
def pop(self, key,
|
|
267
|
+
def pop(self, key, default=_NO_DEFAULT) -> t.Any:
|
|
270
268
|
"""
|
|
271
269
|
Dictionary ``pop`` method. Return and remove the value associated with
|
|
272
270
|
``key``. If more than one value is associated with ``key``, only the
|
|
@@ -288,16 +286,12 @@ class MultiDict(MutableMapping):
|
|
|
288
286
|
...
|
|
289
287
|
KeyError: 'a'
|
|
290
288
|
"""
|
|
291
|
-
if len(args) > 1:
|
|
292
|
-
raise TypeError(
|
|
293
|
-
"pop expected at most 2 arguments, got %d" % (1 + len(args))
|
|
294
|
-
)
|
|
295
289
|
try:
|
|
296
290
|
value = self._dict[key].pop(0)
|
|
297
291
|
except (KeyError, IndexError):
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
292
|
+
if default is _NO_DEFAULT:
|
|
293
|
+
raise KeyError(key)
|
|
294
|
+
return default
|
|
301
295
|
self._order.remove((key, value))
|
|
302
296
|
return value
|
|
303
297
|
|
|
@@ -326,17 +320,9 @@ class MultiDict(MutableMapping):
|
|
|
326
320
|
raise KeyError("popitem(): dictionary is empty")
|
|
327
321
|
return key, self.pop(key)
|
|
328
322
|
|
|
329
|
-
@t.overload
|
|
330
323
|
def update(
|
|
331
|
-
self,
|
|
324
|
+
self, other: SupportsKeysAndGetItemOrIterable = tuple(), /, **kwargs: Any
|
|
332
325
|
) -> None:
|
|
333
|
-
...
|
|
334
|
-
|
|
335
|
-
@t.overload
|
|
336
|
-
def update(self, /, **kwargs: Any) -> None:
|
|
337
|
-
...
|
|
338
|
-
|
|
339
|
-
def update(self, mapping_or_iterable=tuple(), **kwargs):
|
|
340
326
|
r"""
|
|
341
327
|
Update the MultiDict from another MultiDict, regular dictionary or a
|
|
342
328
|
iterable of ``(key, value)`` pairs. New keys overwrite old keys - use
|
|
@@ -371,17 +357,15 @@ class MultiDict(MutableMapping):
|
|
|
371
357
|
>>> d.update(mood='okay')
|
|
372
358
|
|
|
373
359
|
"""
|
|
374
|
-
self._update(
|
|
360
|
+
self._update(other, True, kwargs)
|
|
375
361
|
|
|
376
|
-
def extend(
|
|
377
|
-
self, mapping_or_iterable: SupportsKeysAndGetItemOrIterable = tuple(), **kwargs
|
|
378
|
-
):
|
|
362
|
+
def extend(self, other: SupportsKeysAndGetItemOrIterable = tuple(), **kwargs):
|
|
379
363
|
"""
|
|
380
364
|
Extend the MultiDict with another MultiDict, regular dictionary or a
|
|
381
365
|
iterable of ``(key, value)`` pairs. This is similar to :meth:`update`
|
|
382
366
|
except that new keys are added to old keys.
|
|
383
367
|
"""
|
|
384
|
-
return self._update(
|
|
368
|
+
return self._update(other, False, kwargs)
|
|
385
369
|
|
|
386
370
|
def _update(
|
|
387
371
|
self,
|