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/routeargs.py
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
|
+
from collections.abc import Sequence
|
|
15
16
|
from operator import attrgetter, itemgetter, methodcaller
|
|
16
17
|
from fresco.exceptions import MissingRouteArg
|
|
17
18
|
from fresco.request import Request
|
|
@@ -19,6 +20,7 @@ from itertools import cycle
|
|
|
19
20
|
from typing import Any
|
|
20
21
|
from typing import Callable
|
|
21
22
|
from typing import Mapping
|
|
23
|
+
from typing import Union
|
|
22
24
|
|
|
23
25
|
__all__ = (
|
|
24
26
|
"routearg",
|
|
@@ -138,7 +140,7 @@ def map_magics(dest):
|
|
|
138
140
|
|
|
139
141
|
|
|
140
142
|
@map_magics("raise_exception")
|
|
141
|
-
class LazyException
|
|
143
|
+
class LazyException:
|
|
142
144
|
"""
|
|
143
145
|
A lazy exception uses magic methods to intercept any access to the object
|
|
144
146
|
and raise an exception.
|
|
@@ -151,7 +153,7 @@ class LazyException(object):
|
|
|
151
153
|
raise self.__dict__["_exception"]
|
|
152
154
|
|
|
153
155
|
|
|
154
|
-
class RouteArg
|
|
156
|
+
class RouteArg:
|
|
155
157
|
"""
|
|
156
158
|
RouteArg objects can be used as keyword arguments in a route definition.
|
|
157
159
|
RouteArgs can extract information from the request and make it available
|
|
@@ -194,7 +196,7 @@ class RouteArg(object):
|
|
|
194
196
|
self.route = route
|
|
195
197
|
self.name = name
|
|
196
198
|
|
|
197
|
-
def __call__(self, request):
|
|
199
|
+
def __call__(self, request: Request) -> Any:
|
|
198
200
|
return None
|
|
199
201
|
|
|
200
202
|
|
|
@@ -225,7 +227,7 @@ def routearg(func, *args, **kwargs):
|
|
|
225
227
|
|
|
226
228
|
|
|
227
229
|
class RequestArg(RouteArg):
|
|
228
|
-
"""
|
|
230
|
+
"""
|
|
229
231
|
Extract a view keyword argument from the request object.
|
|
230
232
|
"""
|
|
231
233
|
|
|
@@ -241,9 +243,19 @@ class RequestArg(RouteArg):
|
|
|
241
243
|
#: input
|
|
242
244
|
converter_exceptions = (ValueError, TypeError)
|
|
243
245
|
|
|
246
|
+
converter: Union[
|
|
247
|
+
Callable[[list[str]], Any],
|
|
248
|
+
Callable[[str], Any],
|
|
249
|
+
]
|
|
250
|
+
|
|
244
251
|
def __init__(
|
|
245
252
|
self,
|
|
246
|
-
converter
|
|
253
|
+
converter: Union[
|
|
254
|
+
Sequence[
|
|
255
|
+
Callable[[str], Any],
|
|
256
|
+
],
|
|
257
|
+
Callable[[str], Any]
|
|
258
|
+
] = str,
|
|
247
259
|
key=None,
|
|
248
260
|
default=_marker,
|
|
249
261
|
exception=MissingRouteArg,
|
|
@@ -251,11 +263,13 @@ class RequestArg(RouteArg):
|
|
|
251
263
|
super(RequestArg, self).__init__(default, exception)
|
|
252
264
|
self.formkey = key
|
|
253
265
|
self.getter: Callable[[Mapping], Any]
|
|
254
|
-
self.is_list = isinstance(converter,
|
|
266
|
+
self.is_list = isinstance(converter, Sequence)
|
|
255
267
|
if self.is_list:
|
|
256
|
-
|
|
268
|
+
def list_converter(vs: list[str]) -> list[Any]:
|
|
269
|
+
return [c(v) for c, v in zip(cycle(converter), vs)] # type: ignore
|
|
270
|
+
self.converter = list_converter
|
|
257
271
|
else:
|
|
258
|
-
self.converter = converter
|
|
272
|
+
self.converter = converter # type: ignore
|
|
259
273
|
|
|
260
274
|
def configure(self, route, name):
|
|
261
275
|
if self.formkey is None:
|
|
@@ -372,7 +386,7 @@ class CookieArg(RequestArg):
|
|
|
372
386
|
|
|
373
387
|
source = RequestArg.cookies
|
|
374
388
|
|
|
375
|
-
def getter(self, cookies):
|
|
389
|
+
def getter(self, cookies): # type: ignore
|
|
376
390
|
if self.is_list:
|
|
377
391
|
return cookies.getlist(self.formkey)
|
|
378
392
|
else:
|
fresco/routing.py
CHANGED
|
@@ -19,18 +19,15 @@ import warnings
|
|
|
19
19
|
from copy import copy
|
|
20
20
|
from collections import defaultdict
|
|
21
21
|
from collections import namedtuple
|
|
22
|
+
from collections.abc import Collection
|
|
22
23
|
from collections.abc import MutableSequence
|
|
23
24
|
from importlib import import_module
|
|
24
25
|
from functools import partial
|
|
25
26
|
from typing import Any
|
|
26
27
|
from typing import Callable
|
|
27
|
-
from typing import Dict
|
|
28
|
-
from typing import List
|
|
29
28
|
from typing import Mapping
|
|
30
29
|
from typing import Optional
|
|
31
30
|
from typing import Union
|
|
32
|
-
from typing import Set
|
|
33
|
-
from typing import Tuple
|
|
34
31
|
from weakref import WeakKeyDictionary
|
|
35
32
|
import typing as t
|
|
36
33
|
|
|
@@ -39,7 +36,8 @@ from fresco.response import Response
|
|
|
39
36
|
from fresco.request import Request
|
|
40
37
|
from fresco.requestcontext import context
|
|
41
38
|
from fresco.routeargs import RouteArg
|
|
42
|
-
from fresco.
|
|
39
|
+
from fresco.types import WSGIApplication
|
|
40
|
+
from fresco.types import ViewCallable
|
|
43
41
|
from fresco.util.cache import make_cache
|
|
44
42
|
from fresco.util.common import fq_path
|
|
45
43
|
from fresco.util.urls import join_path
|
|
@@ -178,6 +176,7 @@ class RouteTraversal(
|
|
|
178
176
|
else:
|
|
179
177
|
viewspecs = [viewspec]
|
|
180
178
|
collections_traversed_iter = iter(self.collections_traversed)
|
|
179
|
+
route = None
|
|
181
180
|
for item in viewspecs:
|
|
182
181
|
while True:
|
|
183
182
|
ct = next(collections_traversed_iter, None)
|
|
@@ -247,7 +246,7 @@ class Pattern(object):
|
|
|
247
246
|
URL path.
|
|
248
247
|
"""
|
|
249
248
|
|
|
250
|
-
segments:
|
|
249
|
+
segments: list["PatternSegment"]
|
|
251
250
|
|
|
252
251
|
def match(self, path):
|
|
253
252
|
"""
|
|
@@ -280,8 +279,8 @@ class Pattern(object):
|
|
|
280
279
|
raise NotImplementedError()
|
|
281
280
|
|
|
282
281
|
|
|
283
|
-
class Converter
|
|
284
|
-
"""
|
|
282
|
+
class Converter:
|
|
283
|
+
"""
|
|
285
284
|
Responsible for converting arguments to and from URL components.
|
|
286
285
|
|
|
287
286
|
A ``Converter`` class should provide two instance methods:
|
|
@@ -339,11 +338,11 @@ class StrConverter(Converter):
|
|
|
339
338
|
|
|
340
339
|
pattern = r"[^/]+"
|
|
341
340
|
|
|
342
|
-
def to_string(self,
|
|
341
|
+
def to_string(self, ob):
|
|
343
342
|
"""
|
|
344
343
|
Return ``s`` converted to an ``str`` object.
|
|
345
344
|
"""
|
|
346
|
-
return
|
|
345
|
+
return ob
|
|
347
346
|
|
|
348
347
|
def from_string(self, s):
|
|
349
348
|
"""
|
|
@@ -520,7 +519,15 @@ class ExtensiblePattern(Pattern):
|
|
|
520
519
|
|
|
521
520
|
self.segments = list(self._make_segments())
|
|
522
521
|
self.args = [item for item in self.segments if item.converter is not None]
|
|
523
|
-
|
|
522
|
+
self.segments_from_string = [
|
|
523
|
+
(s.name, s.converter.from_string)
|
|
524
|
+
for s in self.segments
|
|
525
|
+
if s.converter is not None
|
|
526
|
+
]
|
|
527
|
+
self.positional_args = tuple(a.converter for a in self.args if a.name is None)
|
|
528
|
+
self.keyword_args = {
|
|
529
|
+
a.name: a.converter for a in self.args if a.name is not None
|
|
530
|
+
}
|
|
524
531
|
regex = "".join(segment.regex for segment in self.segments)
|
|
525
532
|
if self.match_entire_path:
|
|
526
533
|
regex += "$"
|
|
@@ -531,9 +538,7 @@ class ExtensiblePattern(Pattern):
|
|
|
531
538
|
self.regex_match = self.regex.match
|
|
532
539
|
|
|
533
540
|
def path_argument_info(self):
|
|
534
|
-
|
|
535
|
-
keyword = {a.name: a.converter for a in self.args if a.name is not None}
|
|
536
|
-
return (positional, keyword)
|
|
541
|
+
return (self.positional_args, self.keyword_args)
|
|
537
542
|
|
|
538
543
|
def _make_segments(self):
|
|
539
544
|
r"""
|
|
@@ -596,8 +601,8 @@ class ExtensiblePattern(Pattern):
|
|
|
596
601
|
|
|
597
602
|
try:
|
|
598
603
|
group_items = [
|
|
599
|
-
(
|
|
600
|
-
for value,
|
|
604
|
+
(name, from_string(value))
|
|
605
|
+
for value, (name, from_string) in zip(groups, self.segments_from_string)
|
|
601
606
|
]
|
|
602
607
|
except ValueError:
|
|
603
608
|
return None
|
|
@@ -607,7 +612,9 @@ class ExtensiblePattern(Pattern):
|
|
|
607
612
|
kwargs = {name: value for name, value in group_items if name}
|
|
608
613
|
return PathMatch(matched, path[len(matched) :], args, kwargs)
|
|
609
614
|
|
|
610
|
-
def pathfor(
|
|
615
|
+
def pathfor(
|
|
616
|
+
self, *args, _strjoin="".join, **kwargs
|
|
617
|
+
) -> tuple[str, list[Any], dict[Any, Any]]:
|
|
611
618
|
"""
|
|
612
619
|
Example usage::
|
|
613
620
|
|
|
@@ -621,8 +628,7 @@ class ExtensiblePattern(Pattern):
|
|
|
621
628
|
"""
|
|
622
629
|
|
|
623
630
|
arg_list = list(args)
|
|
624
|
-
|
|
625
|
-
result: List[str] = []
|
|
631
|
+
result: list[str] = []
|
|
626
632
|
result_append = result.append
|
|
627
633
|
for seg in self.segments:
|
|
628
634
|
if not seg.converter:
|
|
@@ -631,7 +637,7 @@ class ExtensiblePattern(Pattern):
|
|
|
631
637
|
elif seg.name:
|
|
632
638
|
try:
|
|
633
639
|
value = kwargs.pop(seg.name)
|
|
634
|
-
except
|
|
640
|
+
except KeyError:
|
|
635
641
|
raise URLGenerationError(
|
|
636
642
|
"Argument %r not specified for url %r"
|
|
637
643
|
% (seg.name, self.pattern)
|
|
@@ -643,11 +649,11 @@ class ExtensiblePattern(Pattern):
|
|
|
643
649
|
value = arg_list.pop(0)
|
|
644
650
|
except IndexError:
|
|
645
651
|
raise URLGenerationError(
|
|
646
|
-
"Not enough positional arguments for url
|
|
652
|
+
f"Not enough positional arguments for url {self.pattern}"
|
|
647
653
|
)
|
|
648
654
|
result_append(seg.converter.to_string(value))
|
|
649
655
|
|
|
650
|
-
return
|
|
656
|
+
return _strjoin(result), arg_list, kwargs
|
|
651
657
|
|
|
652
658
|
def add_prefix(self, prefix):
|
|
653
659
|
return self.__class__(join_path(prefix, self.pattern), self.match_entire_path)
|
|
@@ -680,7 +686,7 @@ class ExtensiblePattern(Pattern):
|
|
|
680
686
|
return "%s" % (self.pattern,)
|
|
681
687
|
|
|
682
688
|
|
|
683
|
-
class PatternSegment
|
|
689
|
+
class PatternSegment:
|
|
684
690
|
"""
|
|
685
691
|
Represent a single segment of a URL pattern, storing information about the
|
|
686
692
|
``source``, ``regex`` used to pattern match the segment, ``name`` for
|
|
@@ -690,7 +696,13 @@ class PatternSegment(object):
|
|
|
690
696
|
|
|
691
697
|
__slots__ = ["source", "regex", "name", "converter"]
|
|
692
698
|
|
|
693
|
-
def __init__(
|
|
699
|
+
def __init__(
|
|
700
|
+
self,
|
|
701
|
+
source: str,
|
|
702
|
+
regex: str,
|
|
703
|
+
name: Optional[str],
|
|
704
|
+
converter: Optional[Converter],
|
|
705
|
+
):
|
|
694
706
|
self.source = source
|
|
695
707
|
self.regex = regex
|
|
696
708
|
self.name = name
|
|
@@ -705,9 +717,9 @@ class Route(object):
|
|
|
705
717
|
#: The default class to use for URL pattern matching
|
|
706
718
|
pattern_class = ExtensiblePattern
|
|
707
719
|
|
|
708
|
-
fallthrough_statuses: Optional[
|
|
720
|
+
fallthrough_statuses: Optional[set[int]]
|
|
709
721
|
|
|
710
|
-
_route_hints:
|
|
722
|
+
_route_hints: dict[Callable, dict[str, list[Callable]]] = defaultdict(
|
|
711
723
|
lambda: defaultdict(list)
|
|
712
724
|
)
|
|
713
725
|
|
|
@@ -716,9 +728,9 @@ class Route(object):
|
|
|
716
728
|
def __init__(
|
|
717
729
|
self,
|
|
718
730
|
pattern: t.Union[str, Pattern],
|
|
719
|
-
methods=None,
|
|
720
|
-
view=None,
|
|
721
|
-
kwargs=None,
|
|
731
|
+
methods: Optional[Collection[str]] = None,
|
|
732
|
+
view: Optional[Union[ViewCallable, "RouteCollection", str]] = None,
|
|
733
|
+
kwargs: Optional[dict[str, Any]] = None,
|
|
722
734
|
args=None,
|
|
723
735
|
name=None,
|
|
724
736
|
predicate=None,
|
|
@@ -726,7 +738,7 @@ class Route(object):
|
|
|
726
738
|
filters=None,
|
|
727
739
|
fallthrough_on=None,
|
|
728
740
|
provide_request: Optional[bool] = None,
|
|
729
|
-
**_kwargs,
|
|
741
|
+
**_kwargs: ViewCallable,
|
|
730
742
|
):
|
|
731
743
|
"""
|
|
732
744
|
:param pattern: A string that can be compiled into a path pattern
|
|
@@ -747,7 +759,7 @@ class Route(object):
|
|
|
747
759
|
before invoking it
|
|
748
760
|
:param filters: Filter functions to apply to the view's return
|
|
749
761
|
value before returning the final response object
|
|
750
|
-
:param fallthrough_on: A
|
|
762
|
+
:param fallthrough_on: A list of http status codes which, if returned
|
|
751
763
|
by a view will cause the current response to be
|
|
752
764
|
discarded with routing continuing to the next
|
|
753
765
|
available route.
|
|
@@ -802,8 +814,9 @@ class Route(object):
|
|
|
802
814
|
'http://localhost/thumbnail'
|
|
803
815
|
|
|
804
816
|
"""
|
|
805
|
-
method_view_map:
|
|
817
|
+
method_view_map: dict[str, t.Union[ViewCallable, RouteCollection, str]] = {}
|
|
806
818
|
if methods:
|
|
819
|
+
assert view is not None
|
|
807
820
|
if isinstance(methods, str):
|
|
808
821
|
methods = [methods]
|
|
809
822
|
else:
|
|
@@ -837,7 +850,7 @@ class Route(object):
|
|
|
837
850
|
self.name = name
|
|
838
851
|
self.predicate = predicate
|
|
839
852
|
self.decorators = decorators or []
|
|
840
|
-
self.before_hooks:
|
|
853
|
+
self.before_hooks: list[Callable] = []
|
|
841
854
|
self.filters = filters or []
|
|
842
855
|
if fallthrough_on:
|
|
843
856
|
self.fallthrough_statuses = {int(i) for i in fallthrough_on}
|
|
@@ -848,14 +861,14 @@ class Route(object):
|
|
|
848
861
|
self.provide_request = provide_request
|
|
849
862
|
|
|
850
863
|
#: Default values to use for path generation
|
|
851
|
-
self.routed_args_default:
|
|
864
|
+
self.routed_args_default: dict[str, Any] = {}
|
|
852
865
|
|
|
853
866
|
self.pattern = pattern
|
|
854
867
|
self.methods = set(method_view_map)
|
|
855
868
|
self.instance = None
|
|
856
869
|
|
|
857
870
|
# Cached references to view functions
|
|
858
|
-
self._cached_views:
|
|
871
|
+
self._cached_views: dict[str, ViewCallable] = {}
|
|
859
872
|
|
|
860
873
|
# Cached references to decorated view function. We use weakrefs in case
|
|
861
874
|
# a process_view hook substitutes the view function used as a key
|
|
@@ -868,8 +881,12 @@ class Route(object):
|
|
|
868
881
|
if default is not _marker:
|
|
869
882
|
self.routed_args_default[k] = default
|
|
870
883
|
|
|
871
|
-
self.view_args
|
|
872
|
-
|
|
884
|
+
self.view_args: tuple[Any] = tuple(
|
|
885
|
+
args or _kwargs.pop("view_args", tuple()) # type: ignore
|
|
886
|
+
)
|
|
887
|
+
self.view_kwargs: dict[str, Any] = dict(
|
|
888
|
+
kwargs or _kwargs.pop("view_kwargs", {}), **_kwargs # type: ignore
|
|
889
|
+
)
|
|
873
890
|
|
|
874
891
|
for arg in self.view_args:
|
|
875
892
|
if isinstance(arg, RouteArg):
|
|
@@ -886,7 +903,9 @@ class Route(object):
|
|
|
886
903
|
self.viewspecs = method_view_map
|
|
887
904
|
|
|
888
905
|
def __repr__(self):
|
|
889
|
-
view_methods_map: Mapping[
|
|
906
|
+
view_methods_map: Mapping[
|
|
907
|
+
t.Union[RouteCollection, ViewCallable, str], set[str]
|
|
908
|
+
] = defaultdict(set)
|
|
890
909
|
for method, viewspec in self.viewspecs.items():
|
|
891
910
|
view_methods_map[viewspec].add(method)
|
|
892
911
|
|
|
@@ -916,7 +935,7 @@ class Route(object):
|
|
|
916
935
|
return None
|
|
917
936
|
return self.pattern.match(path)
|
|
918
937
|
|
|
919
|
-
def getview(self, method: str) ->
|
|
938
|
+
def getview(self, method: str) -> ViewCallable:
|
|
920
939
|
"""
|
|
921
940
|
Resolve and return the raw view callable.
|
|
922
941
|
"""
|
|
@@ -937,14 +956,14 @@ class Route(object):
|
|
|
937
956
|
raise RouteNotReady()
|
|
938
957
|
uview = getattr(self.instance, uview)
|
|
939
958
|
|
|
940
|
-
self._cached_views[method] = uview
|
|
959
|
+
self._cached_views[method] = uview # type: ignore
|
|
941
960
|
if self.provide_request is None:
|
|
942
961
|
if callable(uview):
|
|
943
962
|
self.provide_request = _has_request_parameter(uview)
|
|
944
963
|
else:
|
|
945
964
|
self.provide_request = False
|
|
946
965
|
|
|
947
|
-
return uview
|
|
966
|
+
return uview # type: ignore
|
|
948
967
|
|
|
949
968
|
@classmethod
|
|
950
969
|
def _add_route_hint(cls, viewfunc, hinttype, func):
|
|
@@ -1179,7 +1198,7 @@ class RRoute(Route):
|
|
|
1179
1198
|
|
|
1180
1199
|
def split_iter(pattern, string):
|
|
1181
1200
|
"""
|
|
1182
|
-
Generate alternate strings and match objects for all
|
|
1201
|
+
Generate alternate strings and match objects for all occurences of
|
|
1183
1202
|
``pattern`` in ``string``.
|
|
1184
1203
|
"""
|
|
1185
1204
|
matcher = pattern.finditer(string)
|
|
@@ -1219,10 +1238,8 @@ class RouteCollection(MutableSequence):
|
|
|
1219
1238
|
_route_cache = None
|
|
1220
1239
|
|
|
1221
1240
|
def __init__(self, routes=None, route_class=None, cache=True):
|
|
1222
|
-
self.__routes__:
|
|
1223
|
-
self.__routed_views__:
|
|
1224
|
-
Union[str, Callable], Union[Route, RouteNotFound]
|
|
1225
|
-
] = {}
|
|
1241
|
+
self.__routes__: list[Route] = []
|
|
1242
|
+
self.__routed_views__: dict[Any, Union[Route, RouteNotFound]] = {}
|
|
1226
1243
|
if cache:
|
|
1227
1244
|
self.reinit_route_cache()
|
|
1228
1245
|
self.route_class = route_class or self.route_class
|
|
@@ -1287,8 +1304,8 @@ class RouteCollection(MutableSequence):
|
|
|
1287
1304
|
)
|
|
1288
1305
|
self._route_cache = make_cache(self._get_routes, cache_size)
|
|
1289
1306
|
|
|
1290
|
-
def insert(self,
|
|
1291
|
-
self.__routes__.insert(
|
|
1307
|
+
def insert(self, index, value):
|
|
1308
|
+
self.__routes__.insert(index, value)
|
|
1292
1309
|
if self._route_cache is not None:
|
|
1293
1310
|
self.reinit_route_cache()
|
|
1294
1311
|
|
|
@@ -1451,6 +1468,7 @@ class RouteCollection(MutableSequence):
|
|
|
1451
1468
|
# View function arguments extracted while traversing the path
|
|
1452
1469
|
traversal_args, traversal_kwargs = result.args, result.kwargs
|
|
1453
1470
|
if method is None:
|
|
1471
|
+
view = None
|
|
1454
1472
|
for m in route.viewspecs:
|
|
1455
1473
|
view = route.getview(m)
|
|
1456
1474
|
break
|
|
@@ -1555,8 +1573,8 @@ class RouteCollection(MutableSequence):
|
|
|
1555
1573
|
def route(
|
|
1556
1574
|
self,
|
|
1557
1575
|
pattern: t.Union[str, Pattern],
|
|
1558
|
-
methods: t.Optional[t.Union[str, t.
|
|
1559
|
-
view: t.Optional[t.Union[str,
|
|
1576
|
+
methods: t.Optional[t.Union[str, t.Collection[str]]] = None,
|
|
1577
|
+
view: t.Optional[t.Union[str, ViewCallable]] = None,
|
|
1560
1578
|
*args,
|
|
1561
1579
|
route_class: t.Optional[t.Type[Route]] = None,
|
|
1562
1580
|
**kwargs,
|
|
@@ -1595,7 +1613,7 @@ class RouteCollection(MutableSequence):
|
|
|
1595
1613
|
def route_wsgi(
|
|
1596
1614
|
self,
|
|
1597
1615
|
path: str,
|
|
1598
|
-
wsgiapp: t.Union[
|
|
1616
|
+
wsgiapp: t.Union[WSGIApplication, str],
|
|
1599
1617
|
rewrite_script_name: bool = True,
|
|
1600
1618
|
*args,
|
|
1601
1619
|
**kwargs,
|
|
@@ -1731,7 +1749,7 @@ class RouteCollection(MutableSequence):
|
|
|
1731
1749
|
if r is route:
|
|
1732
1750
|
del self.__routed_views__[k]
|
|
1733
1751
|
|
|
1734
|
-
def remove(self,
|
|
1752
|
+
def remove(self, value):
|
|
1735
1753
|
"""
|
|
1736
1754
|
Remove the route(s) identified by ``viewspec``
|
|
1737
1755
|
|
|
@@ -1739,7 +1757,7 @@ class RouteCollection(MutableSequence):
|
|
|
1739
1757
|
('package.module.view_function'), or the name of a
|
|
1740
1758
|
named route
|
|
1741
1759
|
"""
|
|
1742
|
-
self.replace(
|
|
1760
|
+
self.replace(value, None)
|
|
1743
1761
|
|
|
1744
1762
|
|
|
1745
1763
|
class DelegateRoute(Route):
|
|
@@ -1762,7 +1780,7 @@ class DelegateRoute(Route):
|
|
|
1762
1780
|
view = RouteCollection(routes)
|
|
1763
1781
|
super(DelegateRoute, self).__init__(pattern, ALL_METHODS, view, *args, **kwargs)
|
|
1764
1782
|
|
|
1765
|
-
def _dynamic_routecollectionfactory(self, *args, **kwargs):
|
|
1783
|
+
def _dynamic_routecollectionfactory(self, *args, **kwargs) -> RouteCollection:
|
|
1766
1784
|
"""\
|
|
1767
1785
|
Return the RouteCollection responsible for paths under this route
|
|
1768
1786
|
"""
|
|
@@ -1776,8 +1794,8 @@ class DelegateRoute(Route):
|
|
|
1776
1794
|
(r.bindto(routes) for r in routes.__routes__), cache=False
|
|
1777
1795
|
)
|
|
1778
1796
|
|
|
1779
|
-
def _static_routecollectionfactory(self, *args, **kwargs):
|
|
1780
|
-
return self.getview(GET)
|
|
1797
|
+
def _static_routecollectionfactory(self, *args, **kwargs) -> RouteCollection:
|
|
1798
|
+
return self.getview(GET) # type: ignore
|
|
1781
1799
|
|
|
1782
1800
|
|
|
1783
1801
|
def register_converter(name, registry=ExtensiblePattern):
|
fresco/static.py
CHANGED
|
@@ -57,7 +57,7 @@ def serve_static_file(path, content_type=None, bufsize=8192, **kwargs):
|
|
|
57
57
|
file_wrapper = request.environ.get("wsgi.file_wrapper")
|
|
58
58
|
if file_wrapper is not None:
|
|
59
59
|
|
|
60
|
-
def content_iterator(f):
|
|
60
|
+
def content_iterator(f): # type: ignore
|
|
61
61
|
return file_wrapper(f, bufsize)
|
|
62
62
|
|
|
63
63
|
else:
|
fresco/subrequests.py
CHANGED
fresco/tests/test_core.py
CHANGED
|
@@ -178,10 +178,10 @@ class TestFrescoApp(object):
|
|
|
178
178
|
|
|
179
179
|
def test_get_methods_matches_on_path(self):
|
|
180
180
|
app = FrescoApp()
|
|
181
|
-
app.route("/1", POST,
|
|
182
|
-
app.route("/1", PUT,
|
|
183
|
-
app.route("/2", GET,
|
|
184
|
-
app.route("/2", DELETE,
|
|
181
|
+
app.route("/1", POST, Response)
|
|
182
|
+
app.route("/1", PUT, Response)
|
|
183
|
+
app.route("/2", GET, Response)
|
|
184
|
+
app.route("/2", DELETE, Response)
|
|
185
185
|
|
|
186
186
|
with app.requestcontext() as c:
|
|
187
187
|
assert app.get_methods(c.request, "/1") == set([POST, PUT])
|
fresco/tests/test_multidict.py
CHANGED
|
@@ -88,7 +88,7 @@ class TestMultDict(object):
|
|
|
88
88
|
|
|
89
89
|
def test_keys(self):
|
|
90
90
|
m = MultiDict([("a", 1), ("a", 2), ("b", 3)])
|
|
91
|
-
i = m.keys()
|
|
91
|
+
i = iter(m.keys())
|
|
92
92
|
assert next(i) == "a"
|
|
93
93
|
assert next(i) == "b"
|
|
94
94
|
with pytest.raises(StopIteration):
|
|
@@ -96,7 +96,7 @@ class TestMultDict(object):
|
|
|
96
96
|
|
|
97
97
|
def test_values(self):
|
|
98
98
|
m = MultiDict([("a", 1), ("a", 2), ("b", 3)])
|
|
99
|
-
i = m.values()
|
|
99
|
+
i = iter(m.values())
|
|
100
100
|
assert next(i) == 1
|
|
101
101
|
assert next(i) == 3
|
|
102
102
|
with pytest.raises(StopIteration):
|
fresco/tests/test_options.py
CHANGED
|
@@ -106,7 +106,6 @@ class TestOptions(object):
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
class TestOverrideOptions:
|
|
109
|
-
|
|
110
109
|
def test_override_options_with_object(self):
|
|
111
110
|
options = Options(foo=1)
|
|
112
111
|
with override_options(options, {"foo": 2, "bar": "a"}):
|
|
@@ -163,7 +162,6 @@ class TestLoadKeyValuePairs:
|
|
|
163
162
|
|
|
164
163
|
|
|
165
164
|
class TestLoadOptions:
|
|
166
|
-
|
|
167
165
|
def check_loadoptions(self, tmpdir, files, sources="*", tags=[], expected={}):
|
|
168
166
|
"""
|
|
169
167
|
Write the files indicated in ``sources`` to the given temporary directory,
|
|
@@ -212,6 +210,11 @@ class TestLoadOptions:
|
|
|
212
210
|
def test_it_loads_py_files(self, tmpdir):
|
|
213
211
|
self.check_loadoptions(tmpdir, {"a.py": "x = 2 * 2"}, expected={"x": 4})
|
|
214
212
|
|
|
213
|
+
def test_py_files_have_options_in_namespace(self, tmpdir):
|
|
214
|
+
self.check_loadoptions(
|
|
215
|
+
tmpdir, {"a.py": "options['foo'] = 'bar'"}, expected={"foo": "bar"}
|
|
216
|
+
)
|
|
217
|
+
|
|
215
218
|
def test_it_selects_by_tag(self, tmpdir):
|
|
216
219
|
with self.check_loadoptions(
|
|
217
220
|
tmpdir,
|
|
@@ -237,14 +240,38 @@ class TestLoadOptions:
|
|
|
237
240
|
with self.check_loadoptions(
|
|
238
241
|
tmpdir,
|
|
239
242
|
{
|
|
240
|
-
"a": "a =
|
|
241
|
-
"a.dev.txt": "a = ${a}
|
|
242
|
-
"a.local.txt": "a = ${a}
|
|
243
|
-
"b.dev.txt": "a = ${a}
|
|
243
|
+
"a": "a = 'a'",
|
|
244
|
+
"a.0-dev.txt": "a = ${a} a.dev",
|
|
245
|
+
"a.local.txt": "a = ${a} a.local",
|
|
246
|
+
"b.dev.txt": "a = ${a} b.dev",
|
|
247
|
+
},
|
|
248
|
+
) as loadopts:
|
|
249
|
+
assert loadopts("*", ["dev", "local"]) == {"a": "a a.dev b.dev a.local"}
|
|
250
|
+
assert loadopts("*", ["local", "dev"]) == {"a": "a a.local a.dev b.dev"}
|
|
251
|
+
|
|
252
|
+
def test_it_loads_in_priority_order(self, tmpdir):
|
|
253
|
+
with self.check_loadoptions(
|
|
254
|
+
tmpdir,
|
|
255
|
+
{
|
|
256
|
+
"a": "a = 'a'",
|
|
257
|
+
"a.100-dev.txt": "a = ${a} a.100-dev",
|
|
258
|
+
"a.local.txt": "a = ${a} a.local",
|
|
259
|
+
"b.dev.txt": "a = ${a} b.dev",
|
|
260
|
+
},
|
|
261
|
+
) as loadopts:
|
|
262
|
+
assert loadopts("*", ["dev", "local"]) == {"a": "a b.dev a.local a.100-dev"}
|
|
263
|
+
assert loadopts("*", ["local", "dev"]) == {"a": "a a.local b.dev a.100-dev"}
|
|
264
|
+
|
|
265
|
+
def test_it_loads_in_priority_order_without_tags(self, tmpdir):
|
|
266
|
+
with self.check_loadoptions(
|
|
267
|
+
tmpdir,
|
|
268
|
+
{
|
|
269
|
+
"a": "a = a",
|
|
270
|
+
"b.100": "a = ${a} b",
|
|
271
|
+
"a.200.txt": "a = ${a} 100",
|
|
244
272
|
},
|
|
245
273
|
) as loadopts:
|
|
246
|
-
assert loadopts("*"
|
|
247
|
-
assert loadopts("*", ["local", "dev"]) == {"a": "0-2-1-3"}
|
|
274
|
+
assert loadopts("*") == {"a": "a b 100"}
|
|
248
275
|
|
|
249
276
|
def test_it_loads_from_os_environ(self, tmpdir):
|
|
250
277
|
with setenv(a="2"):
|
|
@@ -272,7 +299,7 @@ class TestLoadOptions:
|
|
|
272
299
|
tmpdir,
|
|
273
300
|
{"a.txt": "a=1", "b.txt": "b=1"},
|
|
274
301
|
sources=["a.*", "b.*"],
|
|
275
|
-
expected={"a": 1, "b": 1}
|
|
302
|
+
expected={"a": 1, "b": 1},
|
|
276
303
|
)
|
|
277
304
|
|
|
278
305
|
def test_it_substitutes_from_environment_variables(self, tmpdir):
|
|
@@ -281,7 +308,7 @@ class TestLoadOptions:
|
|
|
281
308
|
tmpdir,
|
|
282
309
|
{"a.txt": "a=1", "a.bar.txt": "a=2"},
|
|
283
310
|
tags=["{FOO}"],
|
|
284
|
-
expected={"a": 2}
|
|
311
|
+
expected={"a": 2},
|
|
285
312
|
)
|
|
286
313
|
|
|
287
314
|
with setenv(FOO="baz"):
|
|
@@ -289,7 +316,7 @@ class TestLoadOptions:
|
|
|
289
316
|
tmpdir,
|
|
290
317
|
{"a.txt": "a=1", "a.bar.txt": "a=2"},
|
|
291
318
|
tags=["{FOO}"],
|
|
292
|
-
expected={"a": 1}
|
|
319
|
+
expected={"a": 1},
|
|
293
320
|
)
|
|
294
321
|
|
|
295
322
|
def test_it_allows_missing_environment_variables(self, tmpdir):
|
|
@@ -298,19 +325,16 @@ class TestLoadOptions:
|
|
|
298
325
|
tmpdir,
|
|
299
326
|
{"a.txt": "a=1", "a.bar.txt": "a=2"},
|
|
300
327
|
tags=["{FOO}"],
|
|
301
|
-
expected={"a": 1}
|
|
328
|
+
expected={"a": 1},
|
|
302
329
|
)
|
|
303
330
|
|
|
304
331
|
|
|
305
332
|
class TestDictFromOptions:
|
|
306
|
-
|
|
307
333
|
def test_it_splits_on_prefix(self):
|
|
308
|
-
|
|
309
334
|
options = Options(FOO_BAR=1, FOO_BAZ=2, FOO_BAR_BAZ=3, BAR=4)
|
|
310
335
|
assert dict_from_options("FOO_", options) == {"BAR": 1, "BAZ": 2, "BAR_BAZ": 3}
|
|
311
336
|
|
|
312
337
|
def test_it_splits_recursively(self):
|
|
313
|
-
|
|
314
338
|
options = Options(
|
|
315
339
|
A_A=1,
|
|
316
340
|
A_B_C_D=2,
|
|
@@ -323,7 +347,7 @@ class TestDictFromOptions:
|
|
|
323
347
|
"A": 1,
|
|
324
348
|
"B": {"C": {"D": 2}, "E": 3},
|
|
325
349
|
"F": {"G": {"H": 4}},
|
|
326
|
-
"I": 5
|
|
350
|
+
"I": 5,
|
|
327
351
|
}
|
|
328
352
|
|
|
329
353
|
|