fresco 3.3.4__py3-none-any.whl → 3.9.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.
fresco/routing.py CHANGED
@@ -14,22 +14,21 @@
14
14
  #
15
15
  import re
16
16
  import sys
17
+ import inspect
17
18
  import warnings
18
19
  from copy import copy
19
20
  from collections import defaultdict
20
21
  from collections import namedtuple
22
+ from collections.abc import Collection
21
23
  from collections.abc import MutableSequence
24
+ from collections.abc import Sequence
22
25
  from importlib import import_module
23
26
  from functools import partial
24
27
  from typing import Any
25
28
  from typing import Callable
26
- from typing import Dict
27
- from typing import List
28
29
  from typing import Mapping
29
30
  from typing import Optional
30
31
  from typing import Union
31
- from typing import Set
32
- from typing import Tuple
33
32
  from weakref import WeakKeyDictionary
34
33
  import typing as t
35
34
 
@@ -38,6 +37,8 @@ from fresco.response import Response
38
37
  from fresco.request import Request
39
38
  from fresco.requestcontext import context
40
39
  from fresco.routeargs import RouteArg
40
+ from fresco.types import WSGIApplication
41
+ from fresco.types import ViewCallable
41
42
  from fresco.util.cache import make_cache
42
43
  from fresco.util.common import fq_path
43
44
  from fresco.util.urls import join_path
@@ -107,6 +108,9 @@ ALL_METHODS = HTTP_METHODS = set(
107
108
  + RFC5789_METHODS
108
109
  )
109
110
 
111
+ #: Shortcut for specifying all methods as a kwarg
112
+ ALL = "ALL"
113
+
110
114
  __all__ = [
111
115
  "ALL_METHODS",
112
116
  "Pattern",
@@ -125,7 +129,7 @@ PathMatch = namedtuple(
125
129
 
126
130
 
127
131
  class RouteTraversal(
128
- namedtuple("RouteTraversal", "route args kwargs collections_traversed")
132
+ namedtuple("RouteTraversal", "route view args kwargs collections_traversed")
129
133
  ):
130
134
  """
131
135
  Encapsulate a route traversal.
@@ -134,6 +138,7 @@ class RouteTraversal(
134
138
 
135
139
  - ``route`` the final route traversed, allowing access to the view
136
140
  associated with the path.
141
+ - ``view``, the resolved view callable
137
142
  - ``args`` - positional args to be passed to the view. This is a
138
143
  combination of args extracted from the path and any added when
139
144
  constructing the route.
@@ -172,6 +177,7 @@ class RouteTraversal(
172
177
  else:
173
178
  viewspecs = [viewspec]
174
179
  collections_traversed_iter = iter(self.collections_traversed)
180
+ route = None
175
181
  for item in viewspecs:
176
182
  while True:
177
183
  ct = next(collections_traversed_iter, None)
@@ -203,7 +209,7 @@ class RouteTraversal(
203
209
  #: An item of RouteTraversal.collections_traversed
204
210
  TraversedCollection = namedtuple(
205
211
  "TraversedCollection",
206
- "collection path route args kwargs " "traversal_args traversal_kwargs",
212
+ "collection path route args kwargs traversal_args traversal_kwargs",
207
213
  )
208
214
 
209
215
 
@@ -241,7 +247,7 @@ class Pattern(object):
241
247
  URL path.
242
248
  """
243
249
 
244
- segments: t.List["PatternSegment"]
250
+ segments: list["PatternSegment"]
245
251
 
246
252
  def match(self, path):
247
253
  """
@@ -274,8 +280,8 @@ class Pattern(object):
274
280
  raise NotImplementedError()
275
281
 
276
282
 
277
- class Converter(object):
278
- """\
283
+ class Converter:
284
+ """
279
285
  Responsible for converting arguments to and from URL components.
280
286
 
281
287
  A ``Converter`` class should provide two instance methods:
@@ -333,11 +339,11 @@ class StrConverter(Converter):
333
339
 
334
340
  pattern = r"[^/]+"
335
341
 
336
- def to_string(self, s):
342
+ def to_string(self, ob):
337
343
  """
338
344
  Return ``s`` converted to an ``str`` object.
339
345
  """
340
- return s
346
+ return ob
341
347
 
342
348
  def from_string(self, s):
343
349
  """
@@ -514,7 +520,15 @@ class ExtensiblePattern(Pattern):
514
520
 
515
521
  self.segments = list(self._make_segments())
516
522
  self.args = [item for item in self.segments if item.converter is not None]
517
-
523
+ self.segments_from_string = [
524
+ (s.name, s.converter.from_string)
525
+ for s in self.segments
526
+ if s.converter is not None
527
+ ]
528
+ self.positional_args = tuple(a.converter for a in self.args if a.name is None)
529
+ self.keyword_args = {
530
+ a.name: a.converter for a in self.args if a.name is not None
531
+ }
518
532
  regex = "".join(segment.regex for segment in self.segments)
519
533
  if self.match_entire_path:
520
534
  regex += "$"
@@ -525,9 +539,7 @@ class ExtensiblePattern(Pattern):
525
539
  self.regex_match = self.regex.match
526
540
 
527
541
  def path_argument_info(self):
528
- positional = tuple(a.converter for a in self.args if a.name is None)
529
- keyword = {a.name: a.converter for a in self.args if a.name is not None}
530
- return (positional, keyword)
542
+ return (self.positional_args, self.keyword_args)
531
543
 
532
544
  def _make_segments(self):
533
545
  r"""
@@ -590,8 +602,8 @@ class ExtensiblePattern(Pattern):
590
602
 
591
603
  try:
592
604
  group_items = [
593
- (segment.name, segment.converter.from_string(value))
594
- for value, segment in zip(groups, self.args)
605
+ (name, from_string(value))
606
+ for value, (name, from_string) in zip(groups, self.segments_from_string)
595
607
  ]
596
608
  except ValueError:
597
609
  return None
@@ -601,7 +613,9 @@ class ExtensiblePattern(Pattern):
601
613
  kwargs = {name: value for name, value in group_items if name}
602
614
  return PathMatch(matched, path[len(matched) :], args, kwargs)
603
615
 
604
- def pathfor(self, *args, **kwargs) -> Tuple[str, List[Any], Dict[Any, Any]]:
616
+ def pathfor(
617
+ self, *args, _strjoin="".join, **kwargs
618
+ ) -> tuple[str, list[Any], dict[Any, Any]]:
605
619
  """
606
620
  Example usage::
607
621
 
@@ -615,8 +629,7 @@ class ExtensiblePattern(Pattern):
615
629
  """
616
630
 
617
631
  arg_list = list(args)
618
- kwargs = kwargs
619
- result: List[str] = []
632
+ result: list[str] = []
620
633
  result_append = result.append
621
634
  for seg in self.segments:
622
635
  if not seg.converter:
@@ -625,7 +638,7 @@ class ExtensiblePattern(Pattern):
625
638
  elif seg.name:
626
639
  try:
627
640
  value = kwargs.pop(seg.name)
628
- except IndexError:
641
+ except KeyError:
629
642
  raise URLGenerationError(
630
643
  "Argument %r not specified for url %r"
631
644
  % (seg.name, self.pattern)
@@ -637,11 +650,11 @@ class ExtensiblePattern(Pattern):
637
650
  value = arg_list.pop(0)
638
651
  except IndexError:
639
652
  raise URLGenerationError(
640
- "Not enough positional arguments for url %r" % (self.pattern,)
653
+ f"Not enough positional arguments for url {self.pattern}"
641
654
  )
642
655
  result_append(seg.converter.to_string(value))
643
656
 
644
- return "".join(result), arg_list, kwargs
657
+ return _strjoin(result), arg_list, kwargs
645
658
 
646
659
  def add_prefix(self, prefix):
647
660
  return self.__class__(join_path(prefix, self.pattern), self.match_entire_path)
@@ -674,7 +687,7 @@ class ExtensiblePattern(Pattern):
674
687
  return "%s" % (self.pattern,)
675
688
 
676
689
 
677
- class PatternSegment(object):
690
+ class PatternSegment:
678
691
  """
679
692
  Represent a single segment of a URL pattern, storing information about the
680
693
  ``source``, ``regex`` used to pattern match the segment, ``name`` for
@@ -684,7 +697,13 @@ class PatternSegment(object):
684
697
 
685
698
  __slots__ = ["source", "regex", "name", "converter"]
686
699
 
687
- def __init__(self, source, regex, name, converter):
700
+ def __init__(
701
+ self,
702
+ source: str,
703
+ regex: str,
704
+ name: Optional[str],
705
+ converter: Optional[Converter],
706
+ ):
688
707
  self.source = source
689
708
  self.regex = regex
690
709
  self.name = name
@@ -699,35 +718,36 @@ class Route(object):
699
718
  #: The default class to use for URL pattern matching
700
719
  pattern_class = ExtensiblePattern
701
720
 
702
- fallthrough_statuses: Optional[Set[int]]
721
+ fallthrough_statuses: Optional[set[int]]
703
722
 
704
- _route_hints: Dict[Callable, Dict[str, List[Callable]]] = defaultdict(
723
+ _route_hints: dict[Callable, dict[str, list[Callable]]] = defaultdict(
705
724
  lambda: defaultdict(list)
706
725
  )
707
726
 
708
- #: Always provide an positional ``request`` argument to views
709
- provide_request = False
727
+ provide_request: Optional[bool] = None
710
728
 
711
729
  def __init__(
712
730
  self,
713
731
  pattern: t.Union[str, Pattern],
714
- methods=None,
715
- view=None,
716
- kwargs=None,
732
+ methods: Optional[Collection[str]] = None,
733
+ view: Optional[Union[ViewCallable, "RouteCollection", str]] = None,
734
+ kwargs: Optional[dict[str, Any]] = None,
717
735
  args=None,
718
- name=None,
736
+ name: Optional[str] = None,
719
737
  predicate=None,
720
738
  decorators=None,
721
739
  filters=None,
722
- fallthrough_on=None,
740
+ fallthrough_on: Optional[Sequence[int]] = None,
723
741
  provide_request: Optional[bool] = None,
724
- **_kwargs,
742
+ **_kwargs: ViewCallable,
725
743
  ):
726
744
  """
727
745
  :param pattern: A string that can be compiled into a path pattern
728
746
  :param methods: The list of HTTP methods the view is bound to
729
747
  ('GET', 'POST', etc)
730
- :param view: The view function.
748
+ :param view:
749
+ The view function, or a string identifier which will later be resolved.
750
+
731
751
  :param kwargs: A dictionary of default keyword arguments to pass
732
752
  to the view callable
733
753
  :param args: Positional arguments to pass to the view callable
@@ -740,16 +760,19 @@ class Route(object):
740
760
  before invoking it
741
761
  :param filters: Filter functions to apply to the view's return
742
762
  value before returning the final response object
743
- :param fallthrough_on: A List of http status codes which, if returned
763
+ :param fallthrough_on: A list of http status codes which, if returned
744
764
  by a view will cause the current response to be
745
765
  discarded with routing continuing to the next
746
766
  available route.
747
- :param provide_request: If True, provide the current request as the
748
- first argument to the view callable.
767
+ :param provide_request:
768
+ If True, provide the current request as the first argument to the
769
+ view callable. Defaults to ``None``, which will autodetect if the
770
+ view function has an initial parameter of type
771
+ :class:`~fresco.request.Request`
749
772
  :param **_kwargs: Keyword arguments matching HTTP method names
750
773
  (GET, POST etc) can used to specify views
751
774
  associated with those methods.
752
- Other keyword aruments are passed through to the
775
+ Other keyword arguments are passed through to the
753
776
  view callable.
754
777
 
755
778
  Naming routes
@@ -792,8 +815,9 @@ class Route(object):
792
815
  'http://localhost/thumbnail'
793
816
 
794
817
  """
795
- method_view_map: Dict[str, Callable] = {}
818
+ method_view_map: dict[str, t.Union[ViewCallable, RouteCollection, str]] = {}
796
819
  if methods:
820
+ assert view is not None
797
821
  if isinstance(methods, str):
798
822
  methods = [methods]
799
823
  else:
@@ -803,13 +827,25 @@ class Route(object):
803
827
  "HTTP methods must be specified as a string or iterable"
804
828
  )
805
829
  for m in methods:
830
+ if m == ALL:
831
+ methods = ALL_METHODS
832
+ break
806
833
  if m not in ALL_METHODS:
807
834
  raise ValueError("{!r} is not a valid HTTP method".format(m))
808
835
  method_view_map.update((m, view) for m in methods)
809
836
 
837
+ if view := _kwargs.pop("ALL", None):
838
+ method_view_map |= {m: view for m in ALL_METHODS}
839
+
810
840
  for method in ALL_METHODS:
811
841
  if method in _kwargs:
812
842
  method_view_map[method] = _kwargs.pop(method)
843
+ if _kwargs:
844
+ for k in list(_kwargs):
845
+ trymethods = k.split("_")
846
+ if all(m in ALL_METHODS for m in trymethods):
847
+ method_view_map.update((m, _kwargs[k]) for m in trymethods)
848
+ _kwargs.pop(k)
813
849
 
814
850
  if not isinstance(pattern, Pattern):
815
851
  pattern = self.pattern_class(pattern)
@@ -821,7 +857,7 @@ class Route(object):
821
857
  self.name = name
822
858
  self.predicate = predicate
823
859
  self.decorators = decorators or []
824
- self.before_hooks: List[Callable] = []
860
+ self.before_hooks: list[Callable] = []
825
861
  self.filters = filters or []
826
862
  if fallthrough_on:
827
863
  self.fallthrough_statuses = {int(i) for i in fallthrough_on}
@@ -832,14 +868,14 @@ class Route(object):
832
868
  self.provide_request = provide_request
833
869
 
834
870
  #: Default values to use for path generation
835
- self.routed_args_default: Dict[str, Any] = {}
871
+ self.routed_args_default: dict[str, Any] = {}
836
872
 
837
873
  self.pattern = pattern
838
874
  self.methods = set(method_view_map)
839
875
  self.instance = None
840
876
 
841
877
  # Cached references to view functions
842
- self._cached_views: Dict[str, Callable] = {}
878
+ self._cached_views: dict[str, ViewCallable] = {}
843
879
 
844
880
  # Cached references to decorated view function. We use weakrefs in case
845
881
  # a process_view hook substitutes the view function used as a key
@@ -852,8 +888,12 @@ class Route(object):
852
888
  if default is not _marker:
853
889
  self.routed_args_default[k] = default
854
890
 
855
- self.view_args = tuple(args or _kwargs.pop("view_args", tuple()))
856
- self.view_kwargs = dict(kwargs or _kwargs.pop("view_kwargs", {}), **_kwargs)
891
+ self.view_args: tuple[Any] = tuple(
892
+ args or _kwargs.pop("view_args", tuple()) # type: ignore
893
+ )
894
+ self.view_kwargs: dict[str, Any] = dict(
895
+ kwargs or _kwargs.pop("view_kwargs", {}), **_kwargs # type: ignore
896
+ )
857
897
 
858
898
  for arg in self.view_args:
859
899
  if isinstance(arg, RouteArg):
@@ -870,7 +910,9 @@ class Route(object):
870
910
  self.viewspecs = method_view_map
871
911
 
872
912
  def __repr__(self):
873
- view_methods_map: Mapping[Callable, Set[str]] = defaultdict(set)
913
+ view_methods_map: Mapping[
914
+ t.Union[RouteCollection, ViewCallable, str], set[str]
915
+ ] = defaultdict(set)
874
916
  for method, viewspec in self.viewspecs.items():
875
917
  view_methods_map[viewspec].add(method)
876
918
 
@@ -895,14 +937,14 @@ class Route(object):
895
937
  newroute.fallthrough_statuses = {int(s) for s in status_codes}
896
938
  return newroute
897
939
 
898
- def match(self, path, method):
940
+ def match(self, path: str, method: t.Optional[str]) -> t.Optional[PathMatch]:
899
941
  if method and method not in self.methods:
900
942
  return None
901
943
  return self.pattern.match(path)
902
944
 
903
- def getview(self, method: str) -> Callable:
904
- """\
905
- Return the raw view callable.
945
+ def getview(self, method: str) -> ViewCallable:
946
+ """
947
+ Resolve and return the raw view callable.
906
948
  """
907
949
  try:
908
950
  return self._cached_views[method]
@@ -921,8 +963,14 @@ class Route(object):
921
963
  raise RouteNotReady()
922
964
  uview = getattr(self.instance, uview)
923
965
 
924
- self._cached_views[method] = uview
925
- return uview
966
+ self._cached_views[method] = uview # type: ignore
967
+ if self.provide_request is None:
968
+ if callable(uview):
969
+ self.provide_request = _has_request_parameter(uview)
970
+ else:
971
+ self.provide_request = False
972
+
973
+ return uview # type: ignore
926
974
 
927
975
  @classmethod
928
976
  def _add_route_hint(cls, viewfunc, hinttype, func):
@@ -1157,7 +1205,7 @@ class RRoute(Route):
1157
1205
 
1158
1206
  def split_iter(pattern, string):
1159
1207
  """
1160
- Generate alternate strings and match objects for all occurances of
1208
+ Generate alternate strings and match objects for all occurences of
1161
1209
  ``pattern`` in ``string``.
1162
1210
  """
1163
1211
  matcher = pattern.finditer(string)
@@ -1197,10 +1245,8 @@ class RouteCollection(MutableSequence):
1197
1245
  _route_cache = None
1198
1246
 
1199
1247
  def __init__(self, routes=None, route_class=None, cache=True):
1200
- self.__routes__: List[Route] = []
1201
- self.__routed_views__: Dict[
1202
- Union[str, Callable], Union[Route, RouteNotFound]
1203
- ] = {}
1248
+ self.__routes__: list[Route] = []
1249
+ self.__routed_views__: dict[Any, Union[Route, RouteNotFound]] = {}
1204
1250
  if cache:
1205
1251
  self.reinit_route_cache()
1206
1252
  self.route_class = route_class or self.route_class
@@ -1265,8 +1311,8 @@ class RouteCollection(MutableSequence):
1265
1311
  )
1266
1312
  self._route_cache = make_cache(self._get_routes, cache_size)
1267
1313
 
1268
- def insert(self, position, item):
1269
- self.__routes__.insert(position, item)
1314
+ def insert(self, index, value):
1315
+ self.__routes__.insert(index, value)
1270
1316
  if self._route_cache is not None:
1271
1317
  self.reinit_route_cache()
1272
1318
 
@@ -1385,8 +1431,8 @@ class RouteCollection(MutableSequence):
1385
1431
  raise exc
1386
1432
 
1387
1433
  def _get_routes(
1388
- self, key: Tuple[t.Optional[str], str]
1389
- ) -> t.Sequence[t.Tuple[Route, PathMatch]]:
1434
+ self, key: tuple[t.Optional[str], str]
1435
+ ) -> t.Sequence[tuple[Route, PathMatch]]:
1390
1436
  method, path = key
1391
1437
  routes = ((r, r.match(path, method)) for r in self.__routes__)
1392
1438
  return [(r, t) for (r, t) in routes if t is not None]
@@ -1428,6 +1474,13 @@ class RouteCollection(MutableSequence):
1428
1474
 
1429
1475
  # View function arguments extracted while traversing the path
1430
1476
  traversal_args, traversal_kwargs = result.args, result.kwargs
1477
+ if method is None:
1478
+ view = None
1479
+ for m in route.viewspecs:
1480
+ view = route.getview(m)
1481
+ break
1482
+ else:
1483
+ view = route.getview(method)
1431
1484
 
1432
1485
  # Process any args/kwargs defined in the Route declaration.
1433
1486
  if request:
@@ -1459,7 +1512,7 @@ class RouteCollection(MutableSequence):
1459
1512
  raise exc
1460
1513
 
1461
1514
  r = self.route_class("/", ALL_METHODS, raiser)
1462
- yield RouteTraversal(r, (), {}, [(self, "", r)])
1515
+ yield RouteTraversal(r, r.getview("GET"), (), {}, [(self, "", r)])
1463
1516
  continue
1464
1517
 
1465
1518
  for sub in sub_routes.get_route_traversals(
@@ -1484,17 +1537,21 @@ class RouteCollection(MutableSequence):
1484
1537
  # Dynamic routes consume their arguments when creating the
1485
1538
  # sub RouteCollection.
1486
1539
  if route.dynamic:
1487
- yield RouteTraversal(sub.route, sub.args, sub.kwargs, traversed)
1540
+ yield RouteTraversal(
1541
+ sub.route, sub.view, sub.args, sub.kwargs, traversed
1542
+ )
1488
1543
  else:
1489
1544
  yield RouteTraversal(
1490
1545
  sub.route,
1546
+ sub.view,
1491
1547
  args + sub.args,
1492
- dict(kwargs, **sub.kwargs),
1548
+ kwargs | sub.kwargs,
1493
1549
  traversed,
1494
1550
  )
1495
1551
  else:
1496
1552
  yield RouteTraversal(
1497
1553
  route,
1554
+ view,
1498
1555
  args,
1499
1556
  kwargs,
1500
1557
  [
@@ -1523,8 +1580,8 @@ class RouteCollection(MutableSequence):
1523
1580
  def route(
1524
1581
  self,
1525
1582
  pattern: t.Union[str, Pattern],
1526
- methods: t.Optional[t.Union[str, t.Iterable[str]]] = None,
1527
- view: t.Optional[t.Union[str, t.Callable]] = None,
1583
+ methods: t.Optional[t.Union[str, t.Collection[str]]] = None,
1584
+ view: t.Optional[t.Union[str, ViewCallable]] = None,
1528
1585
  *args,
1529
1586
  route_class: t.Optional[t.Type[Route]] = None,
1530
1587
  **kwargs,
@@ -1563,7 +1620,7 @@ class RouteCollection(MutableSequence):
1563
1620
  def route_wsgi(
1564
1621
  self,
1565
1622
  path: str,
1566
- wsgiapp: t.Union[t.Callable, str],
1623
+ wsgiapp: t.Union[WSGIApplication, str],
1567
1624
  rewrite_script_name: bool = True,
1568
1625
  *args,
1569
1626
  **kwargs,
@@ -1591,7 +1648,7 @@ class RouteCollection(MutableSequence):
1591
1648
  resolved_wsgi_app = None
1592
1649
 
1593
1650
  def fresco_wsgi_view(
1594
- request,
1651
+ request: Request,
1595
1652
  path=path,
1596
1653
  ws_path=ws_path,
1597
1654
  rewrite_script_name=rewrite_script_name,
@@ -1699,7 +1756,7 @@ class RouteCollection(MutableSequence):
1699
1756
  if r is route:
1700
1757
  del self.__routed_views__[k]
1701
1758
 
1702
- def remove(self, viewspec):
1759
+ def remove(self, value):
1703
1760
  """
1704
1761
  Remove the route(s) identified by ``viewspec``
1705
1762
 
@@ -1707,7 +1764,7 @@ class RouteCollection(MutableSequence):
1707
1764
  ('package.module.view_function'), or the name of a
1708
1765
  named route
1709
1766
  """
1710
- self.replace(viewspec, None)
1767
+ self.replace(value, None)
1711
1768
 
1712
1769
 
1713
1770
  class DelegateRoute(Route):
@@ -1730,7 +1787,7 @@ class DelegateRoute(Route):
1730
1787
  view = RouteCollection(routes)
1731
1788
  super(DelegateRoute, self).__init__(pattern, ALL_METHODS, view, *args, **kwargs)
1732
1789
 
1733
- def _dynamic_routecollectionfactory(self, *args, **kwargs):
1790
+ def _dynamic_routecollectionfactory(self, *args, **kwargs) -> RouteCollection:
1734
1791
  """\
1735
1792
  Return the RouteCollection responsible for paths under this route
1736
1793
  """
@@ -1744,8 +1801,8 @@ class DelegateRoute(Route):
1744
1801
  (r.bindto(routes) for r in routes.__routes__), cache=False
1745
1802
  )
1746
1803
 
1747
- def _static_routecollectionfactory(self, *args, **kwargs):
1748
- return self.getview(GET)
1804
+ def _static_routecollectionfactory(self, *args, **kwargs) -> RouteCollection:
1805
+ return self.getview(GET) # type: ignore
1749
1806
 
1750
1807
 
1751
1808
  def register_converter(name, registry=ExtensiblePattern):
@@ -1766,3 +1823,24 @@ def register_converter(name, registry=ExtensiblePattern):
1766
1823
  return cls
1767
1824
 
1768
1825
  return register_converter
1826
+
1827
+
1828
+ def _has_request_parameter(func: Callable[..., Any]) -> bool:
1829
+ """
1830
+ Return True if the given function has an initial parameter of type Request
1831
+ """
1832
+
1833
+ def _is_request_annotation(a: Any) -> bool:
1834
+ if isinstance(a, type) and issubclass(a, Request):
1835
+ return True
1836
+
1837
+ origin = t.get_origin(a)
1838
+ if origin is t.Union and any(
1839
+ _is_request_annotation(arg) for arg in t.get_args(a)
1840
+ ):
1841
+ return True
1842
+ return False
1843
+
1844
+ sig = inspect.signature(func)
1845
+ firstparam = next(iter(sig.parameters.values()), None)
1846
+ return bool(firstparam and _is_request_annotation(firstparam.annotation))
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
@@ -1,7 +1,5 @@
1
1
  from itertools import chain
2
2
  from typing import Any
3
- from typing import Dict
4
- from typing import Tuple
5
3
 
6
4
  from fresco.core import context
7
5
  from fresco.request import Request
@@ -174,7 +172,7 @@ def subrequest_raw(view, *args, **kwargs) -> Response:
174
172
 
175
173
  response = view(*args, **kwargs)
176
174
 
177
- return response
175
+ return response # type: ignore
178
176
  finally:
179
177
  context.pop()
180
178
 
@@ -262,8 +260,8 @@ def resolve_viewspec(viewspec, *args, **kwargs):
262
260
 
263
261
 
264
262
  def _get_args_for_route(
265
- route: Route, request: Request, args: Tuple, kwargs: Dict[str, Any]
266
- ) -> Tuple[Tuple, Dict[str, Any]]:
263
+ route: Route, request: Request, args: tuple[Any, ...], kwargs: dict[str, Any]
264
+ ) -> tuple[tuple[Any, ...], dict[str, Any]]:
267
265
  """
268
266
  Return the args/kwargs required to pass to the view callable for ``route``.
269
267
  """
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, lambda: None)
182
- app.route("/1", PUT, lambda: None)
183
- app.route("/2", GET, lambda: None)
184
- app.route("/2", DELETE, lambda: None)
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])
@@ -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):