fresco 3.4.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/routing.py CHANGED
@@ -14,22 +14,20 @@
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
22
24
  from importlib import import_module
23
25
  from functools import partial
24
26
  from typing import Any
25
27
  from typing import Callable
26
- from typing import Dict
27
- from typing import List
28
28
  from typing import Mapping
29
29
  from typing import Optional
30
30
  from typing import Union
31
- from typing import Set
32
- from typing import Tuple
33
31
  from weakref import WeakKeyDictionary
34
32
  import typing as t
35
33
 
@@ -38,6 +36,8 @@ from fresco.response import Response
38
36
  from fresco.request import Request
39
37
  from fresco.requestcontext import context
40
38
  from fresco.routeargs import RouteArg
39
+ from fresco.types import WSGIApplication
40
+ from fresco.types import ViewCallable
41
41
  from fresco.util.cache import make_cache
42
42
  from fresco.util.common import fq_path
43
43
  from fresco.util.urls import join_path
@@ -128,7 +128,7 @@ PathMatch = namedtuple(
128
128
 
129
129
 
130
130
  class RouteTraversal(
131
- namedtuple("RouteTraversal", "route args kwargs collections_traversed")
131
+ namedtuple("RouteTraversal", "route view args kwargs collections_traversed")
132
132
  ):
133
133
  """
134
134
  Encapsulate a route traversal.
@@ -137,6 +137,7 @@ class RouteTraversal(
137
137
 
138
138
  - ``route`` the final route traversed, allowing access to the view
139
139
  associated with the path.
140
+ - ``view``, the resolved view callable
140
141
  - ``args`` - positional args to be passed to the view. This is a
141
142
  combination of args extracted from the path and any added when
142
143
  constructing the route.
@@ -175,6 +176,7 @@ class RouteTraversal(
175
176
  else:
176
177
  viewspecs = [viewspec]
177
178
  collections_traversed_iter = iter(self.collections_traversed)
179
+ route = None
178
180
  for item in viewspecs:
179
181
  while True:
180
182
  ct = next(collections_traversed_iter, None)
@@ -206,7 +208,7 @@ class RouteTraversal(
206
208
  #: An item of RouteTraversal.collections_traversed
207
209
  TraversedCollection = namedtuple(
208
210
  "TraversedCollection",
209
- "collection path route args kwargs " "traversal_args traversal_kwargs",
211
+ "collection path route args kwargs traversal_args traversal_kwargs",
210
212
  )
211
213
 
212
214
 
@@ -244,7 +246,7 @@ class Pattern(object):
244
246
  URL path.
245
247
  """
246
248
 
247
- segments: t.List["PatternSegment"]
249
+ segments: list["PatternSegment"]
248
250
 
249
251
  def match(self, path):
250
252
  """
@@ -277,8 +279,8 @@ class Pattern(object):
277
279
  raise NotImplementedError()
278
280
 
279
281
 
280
- class Converter(object):
281
- """\
282
+ class Converter:
283
+ """
282
284
  Responsible for converting arguments to and from URL components.
283
285
 
284
286
  A ``Converter`` class should provide two instance methods:
@@ -336,11 +338,11 @@ class StrConverter(Converter):
336
338
 
337
339
  pattern = r"[^/]+"
338
340
 
339
- def to_string(self, s):
341
+ def to_string(self, ob):
340
342
  """
341
343
  Return ``s`` converted to an ``str`` object.
342
344
  """
343
- return s
345
+ return ob
344
346
 
345
347
  def from_string(self, s):
346
348
  """
@@ -517,7 +519,15 @@ class ExtensiblePattern(Pattern):
517
519
 
518
520
  self.segments = list(self._make_segments())
519
521
  self.args = [item for item in self.segments if item.converter is not None]
520
-
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
+ }
521
531
  regex = "".join(segment.regex for segment in self.segments)
522
532
  if self.match_entire_path:
523
533
  regex += "$"
@@ -528,9 +538,7 @@ class ExtensiblePattern(Pattern):
528
538
  self.regex_match = self.regex.match
529
539
 
530
540
  def path_argument_info(self):
531
- positional = tuple(a.converter for a in self.args if a.name is None)
532
- keyword = {a.name: a.converter for a in self.args if a.name is not None}
533
- return (positional, keyword)
541
+ return (self.positional_args, self.keyword_args)
534
542
 
535
543
  def _make_segments(self):
536
544
  r"""
@@ -593,8 +601,8 @@ class ExtensiblePattern(Pattern):
593
601
 
594
602
  try:
595
603
  group_items = [
596
- (segment.name, segment.converter.from_string(value))
597
- for value, segment in zip(groups, self.args)
604
+ (name, from_string(value))
605
+ for value, (name, from_string) in zip(groups, self.segments_from_string)
598
606
  ]
599
607
  except ValueError:
600
608
  return None
@@ -604,7 +612,9 @@ class ExtensiblePattern(Pattern):
604
612
  kwargs = {name: value for name, value in group_items if name}
605
613
  return PathMatch(matched, path[len(matched) :], args, kwargs)
606
614
 
607
- def pathfor(self, *args, **kwargs) -> Tuple[str, List[Any], Dict[Any, Any]]:
615
+ def pathfor(
616
+ self, *args, _strjoin="".join, **kwargs
617
+ ) -> tuple[str, list[Any], dict[Any, Any]]:
608
618
  """
609
619
  Example usage::
610
620
 
@@ -618,8 +628,7 @@ class ExtensiblePattern(Pattern):
618
628
  """
619
629
 
620
630
  arg_list = list(args)
621
- kwargs = kwargs
622
- result: List[str] = []
631
+ result: list[str] = []
623
632
  result_append = result.append
624
633
  for seg in self.segments:
625
634
  if not seg.converter:
@@ -628,7 +637,7 @@ class ExtensiblePattern(Pattern):
628
637
  elif seg.name:
629
638
  try:
630
639
  value = kwargs.pop(seg.name)
631
- except IndexError:
640
+ except KeyError:
632
641
  raise URLGenerationError(
633
642
  "Argument %r not specified for url %r"
634
643
  % (seg.name, self.pattern)
@@ -640,11 +649,11 @@ class ExtensiblePattern(Pattern):
640
649
  value = arg_list.pop(0)
641
650
  except IndexError:
642
651
  raise URLGenerationError(
643
- "Not enough positional arguments for url %r" % (self.pattern,)
652
+ f"Not enough positional arguments for url {self.pattern}"
644
653
  )
645
654
  result_append(seg.converter.to_string(value))
646
655
 
647
- return "".join(result), arg_list, kwargs
656
+ return _strjoin(result), arg_list, kwargs
648
657
 
649
658
  def add_prefix(self, prefix):
650
659
  return self.__class__(join_path(prefix, self.pattern), self.match_entire_path)
@@ -677,7 +686,7 @@ class ExtensiblePattern(Pattern):
677
686
  return "%s" % (self.pattern,)
678
687
 
679
688
 
680
- class PatternSegment(object):
689
+ class PatternSegment:
681
690
  """
682
691
  Represent a single segment of a URL pattern, storing information about the
683
692
  ``source``, ``regex`` used to pattern match the segment, ``name`` for
@@ -687,7 +696,13 @@ class PatternSegment(object):
687
696
 
688
697
  __slots__ = ["source", "regex", "name", "converter"]
689
698
 
690
- def __init__(self, source, regex, name, converter):
699
+ def __init__(
700
+ self,
701
+ source: str,
702
+ regex: str,
703
+ name: Optional[str],
704
+ converter: Optional[Converter],
705
+ ):
691
706
  self.source = source
692
707
  self.regex = regex
693
708
  self.name = name
@@ -702,21 +717,20 @@ class Route(object):
702
717
  #: The default class to use for URL pattern matching
703
718
  pattern_class = ExtensiblePattern
704
719
 
705
- fallthrough_statuses: Optional[Set[int]]
720
+ fallthrough_statuses: Optional[set[int]]
706
721
 
707
- _route_hints: Dict[Callable, Dict[str, List[Callable]]] = defaultdict(
722
+ _route_hints: dict[Callable, dict[str, list[Callable]]] = defaultdict(
708
723
  lambda: defaultdict(list)
709
724
  )
710
725
 
711
- #: Always provide an positional ``request`` argument to views
712
- provide_request = False
726
+ provide_request: Optional[bool] = None
713
727
 
714
728
  def __init__(
715
729
  self,
716
730
  pattern: t.Union[str, Pattern],
717
- methods=None,
718
- view=None,
719
- kwargs=None,
731
+ methods: Optional[Collection[str]] = None,
732
+ view: Optional[Union[ViewCallable, "RouteCollection", str]] = None,
733
+ kwargs: Optional[dict[str, Any]] = None,
720
734
  args=None,
721
735
  name=None,
722
736
  predicate=None,
@@ -724,13 +738,15 @@ class Route(object):
724
738
  filters=None,
725
739
  fallthrough_on=None,
726
740
  provide_request: Optional[bool] = None,
727
- **_kwargs,
741
+ **_kwargs: ViewCallable,
728
742
  ):
729
743
  """
730
744
  :param pattern: A string that can be compiled into a path pattern
731
745
  :param methods: The list of HTTP methods the view is bound to
732
746
  ('GET', 'POST', etc)
733
- :param view: The view function.
747
+ :param view:
748
+ The view function, or a string identifier which will later be resolved.
749
+
734
750
  :param kwargs: A dictionary of default keyword arguments to pass
735
751
  to the view callable
736
752
  :param args: Positional arguments to pass to the view callable
@@ -743,12 +759,15 @@ class Route(object):
743
759
  before invoking it
744
760
  :param filters: Filter functions to apply to the view's return
745
761
  value before returning the final response object
746
- :param fallthrough_on: A List of http status codes which, if returned
762
+ :param fallthrough_on: A list of http status codes which, if returned
747
763
  by a view will cause the current response to be
748
764
  discarded with routing continuing to the next
749
765
  available route.
750
- :param provide_request: If True, provide the current request as the
751
- first argument to the view callable.
766
+ :param provide_request:
767
+ If True, provide the current request as the first argument to the
768
+ view callable. Defaults to ``None``, which will autodetect if the
769
+ view function has an initial parameter of type
770
+ :class:`~fresco.request.Request`
752
771
  :param **_kwargs: Keyword arguments matching HTTP method names
753
772
  (GET, POST etc) can used to specify views
754
773
  associated with those methods.
@@ -795,8 +814,9 @@ class Route(object):
795
814
  'http://localhost/thumbnail'
796
815
 
797
816
  """
798
- method_view_map: Dict[str, Callable] = {}
817
+ method_view_map: dict[str, t.Union[ViewCallable, RouteCollection, str]] = {}
799
818
  if methods:
819
+ assert view is not None
800
820
  if isinstance(methods, str):
801
821
  methods = [methods]
802
822
  else:
@@ -830,7 +850,7 @@ class Route(object):
830
850
  self.name = name
831
851
  self.predicate = predicate
832
852
  self.decorators = decorators or []
833
- self.before_hooks: List[Callable] = []
853
+ self.before_hooks: list[Callable] = []
834
854
  self.filters = filters or []
835
855
  if fallthrough_on:
836
856
  self.fallthrough_statuses = {int(i) for i in fallthrough_on}
@@ -841,14 +861,14 @@ class Route(object):
841
861
  self.provide_request = provide_request
842
862
 
843
863
  #: Default values to use for path generation
844
- self.routed_args_default: Dict[str, Any] = {}
864
+ self.routed_args_default: dict[str, Any] = {}
845
865
 
846
866
  self.pattern = pattern
847
867
  self.methods = set(method_view_map)
848
868
  self.instance = None
849
869
 
850
870
  # Cached references to view functions
851
- self._cached_views: Dict[str, Callable] = {}
871
+ self._cached_views: dict[str, ViewCallable] = {}
852
872
 
853
873
  # Cached references to decorated view function. We use weakrefs in case
854
874
  # a process_view hook substitutes the view function used as a key
@@ -861,8 +881,12 @@ class Route(object):
861
881
  if default is not _marker:
862
882
  self.routed_args_default[k] = default
863
883
 
864
- self.view_args = tuple(args or _kwargs.pop("view_args", tuple()))
865
- self.view_kwargs = dict(kwargs or _kwargs.pop("view_kwargs", {}), **_kwargs)
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
+ )
866
890
 
867
891
  for arg in self.view_args:
868
892
  if isinstance(arg, RouteArg):
@@ -879,7 +903,9 @@ class Route(object):
879
903
  self.viewspecs = method_view_map
880
904
 
881
905
  def __repr__(self):
882
- view_methods_map: Mapping[Callable, Set[str]] = defaultdict(set)
906
+ view_methods_map: Mapping[
907
+ t.Union[RouteCollection, ViewCallable, str], set[str]
908
+ ] = defaultdict(set)
883
909
  for method, viewspec in self.viewspecs.items():
884
910
  view_methods_map[viewspec].add(method)
885
911
 
@@ -904,14 +930,14 @@ class Route(object):
904
930
  newroute.fallthrough_statuses = {int(s) for s in status_codes}
905
931
  return newroute
906
932
 
907
- def match(self, path, method):
933
+ def match(self, path: str, method: t.Optional[str]) -> t.Optional[PathMatch]:
908
934
  if method and method not in self.methods:
909
935
  return None
910
936
  return self.pattern.match(path)
911
937
 
912
- def getview(self, method: str) -> Callable:
913
- """\
914
- Return the raw view callable.
938
+ def getview(self, method: str) -> ViewCallable:
939
+ """
940
+ Resolve and return the raw view callable.
915
941
  """
916
942
  try:
917
943
  return self._cached_views[method]
@@ -930,8 +956,14 @@ class Route(object):
930
956
  raise RouteNotReady()
931
957
  uview = getattr(self.instance, uview)
932
958
 
933
- self._cached_views[method] = uview
934
- return uview
959
+ self._cached_views[method] = uview # type: ignore
960
+ if self.provide_request is None:
961
+ if callable(uview):
962
+ self.provide_request = _has_request_parameter(uview)
963
+ else:
964
+ self.provide_request = False
965
+
966
+ return uview # type: ignore
935
967
 
936
968
  @classmethod
937
969
  def _add_route_hint(cls, viewfunc, hinttype, func):
@@ -1166,7 +1198,7 @@ class RRoute(Route):
1166
1198
 
1167
1199
  def split_iter(pattern, string):
1168
1200
  """
1169
- Generate alternate strings and match objects for all occurances of
1201
+ Generate alternate strings and match objects for all occurences of
1170
1202
  ``pattern`` in ``string``.
1171
1203
  """
1172
1204
  matcher = pattern.finditer(string)
@@ -1206,10 +1238,8 @@ class RouteCollection(MutableSequence):
1206
1238
  _route_cache = None
1207
1239
 
1208
1240
  def __init__(self, routes=None, route_class=None, cache=True):
1209
- self.__routes__: List[Route] = []
1210
- self.__routed_views__: Dict[
1211
- Union[str, Callable], Union[Route, RouteNotFound]
1212
- ] = {}
1241
+ self.__routes__: list[Route] = []
1242
+ self.__routed_views__: dict[Any, Union[Route, RouteNotFound]] = {}
1213
1243
  if cache:
1214
1244
  self.reinit_route_cache()
1215
1245
  self.route_class = route_class or self.route_class
@@ -1274,8 +1304,8 @@ class RouteCollection(MutableSequence):
1274
1304
  )
1275
1305
  self._route_cache = make_cache(self._get_routes, cache_size)
1276
1306
 
1277
- def insert(self, position, item):
1278
- self.__routes__.insert(position, item)
1307
+ def insert(self, index, value):
1308
+ self.__routes__.insert(index, value)
1279
1309
  if self._route_cache is not None:
1280
1310
  self.reinit_route_cache()
1281
1311
 
@@ -1394,8 +1424,8 @@ class RouteCollection(MutableSequence):
1394
1424
  raise exc
1395
1425
 
1396
1426
  def _get_routes(
1397
- self, key: Tuple[t.Optional[str], str]
1398
- ) -> t.Sequence[t.Tuple[Route, PathMatch]]:
1427
+ self, key: tuple[t.Optional[str], str]
1428
+ ) -> t.Sequence[tuple[Route, PathMatch]]:
1399
1429
  method, path = key
1400
1430
  routes = ((r, r.match(path, method)) for r in self.__routes__)
1401
1431
  return [(r, t) for (r, t) in routes if t is not None]
@@ -1437,6 +1467,13 @@ class RouteCollection(MutableSequence):
1437
1467
 
1438
1468
  # View function arguments extracted while traversing the path
1439
1469
  traversal_args, traversal_kwargs = result.args, result.kwargs
1470
+ if method is None:
1471
+ view = None
1472
+ for m in route.viewspecs:
1473
+ view = route.getview(m)
1474
+ break
1475
+ else:
1476
+ view = route.getview(method)
1440
1477
 
1441
1478
  # Process any args/kwargs defined in the Route declaration.
1442
1479
  if request:
@@ -1468,7 +1505,7 @@ class RouteCollection(MutableSequence):
1468
1505
  raise exc
1469
1506
 
1470
1507
  r = self.route_class("/", ALL_METHODS, raiser)
1471
- yield RouteTraversal(r, (), {}, [(self, "", r)])
1508
+ yield RouteTraversal(r, r.getview("GET"), (), {}, [(self, "", r)])
1472
1509
  continue
1473
1510
 
1474
1511
  for sub in sub_routes.get_route_traversals(
@@ -1493,17 +1530,21 @@ class RouteCollection(MutableSequence):
1493
1530
  # Dynamic routes consume their arguments when creating the
1494
1531
  # sub RouteCollection.
1495
1532
  if route.dynamic:
1496
- yield RouteTraversal(sub.route, sub.args, sub.kwargs, traversed)
1533
+ yield RouteTraversal(
1534
+ sub.route, sub.view, sub.args, sub.kwargs, traversed
1535
+ )
1497
1536
  else:
1498
1537
  yield RouteTraversal(
1499
1538
  sub.route,
1539
+ sub.view,
1500
1540
  args + sub.args,
1501
- dict(kwargs, **sub.kwargs),
1541
+ kwargs | sub.kwargs,
1502
1542
  traversed,
1503
1543
  )
1504
1544
  else:
1505
1545
  yield RouteTraversal(
1506
1546
  route,
1547
+ view,
1507
1548
  args,
1508
1549
  kwargs,
1509
1550
  [
@@ -1532,8 +1573,8 @@ class RouteCollection(MutableSequence):
1532
1573
  def route(
1533
1574
  self,
1534
1575
  pattern: t.Union[str, Pattern],
1535
- methods: t.Optional[t.Union[str, t.Iterable[str]]] = None,
1536
- view: t.Optional[t.Union[str, t.Callable]] = None,
1576
+ methods: t.Optional[t.Union[str, t.Collection[str]]] = None,
1577
+ view: t.Optional[t.Union[str, ViewCallable]] = None,
1537
1578
  *args,
1538
1579
  route_class: t.Optional[t.Type[Route]] = None,
1539
1580
  **kwargs,
@@ -1572,7 +1613,7 @@ class RouteCollection(MutableSequence):
1572
1613
  def route_wsgi(
1573
1614
  self,
1574
1615
  path: str,
1575
- wsgiapp: t.Union[t.Callable, str],
1616
+ wsgiapp: t.Union[WSGIApplication, str],
1576
1617
  rewrite_script_name: bool = True,
1577
1618
  *args,
1578
1619
  **kwargs,
@@ -1600,7 +1641,7 @@ class RouteCollection(MutableSequence):
1600
1641
  resolved_wsgi_app = None
1601
1642
 
1602
1643
  def fresco_wsgi_view(
1603
- request,
1644
+ request: Request,
1604
1645
  path=path,
1605
1646
  ws_path=ws_path,
1606
1647
  rewrite_script_name=rewrite_script_name,
@@ -1708,7 +1749,7 @@ class RouteCollection(MutableSequence):
1708
1749
  if r is route:
1709
1750
  del self.__routed_views__[k]
1710
1751
 
1711
- def remove(self, viewspec):
1752
+ def remove(self, value):
1712
1753
  """
1713
1754
  Remove the route(s) identified by ``viewspec``
1714
1755
 
@@ -1716,7 +1757,7 @@ class RouteCollection(MutableSequence):
1716
1757
  ('package.module.view_function'), or the name of a
1717
1758
  named route
1718
1759
  """
1719
- self.replace(viewspec, None)
1760
+ self.replace(value, None)
1720
1761
 
1721
1762
 
1722
1763
  class DelegateRoute(Route):
@@ -1739,7 +1780,7 @@ class DelegateRoute(Route):
1739
1780
  view = RouteCollection(routes)
1740
1781
  super(DelegateRoute, self).__init__(pattern, ALL_METHODS, view, *args, **kwargs)
1741
1782
 
1742
- def _dynamic_routecollectionfactory(self, *args, **kwargs):
1783
+ def _dynamic_routecollectionfactory(self, *args, **kwargs) -> RouteCollection:
1743
1784
  """\
1744
1785
  Return the RouteCollection responsible for paths under this route
1745
1786
  """
@@ -1753,8 +1794,8 @@ class DelegateRoute(Route):
1753
1794
  (r.bindto(routes) for r in routes.__routes__), cache=False
1754
1795
  )
1755
1796
 
1756
- def _static_routecollectionfactory(self, *args, **kwargs):
1757
- return self.getview(GET)
1797
+ def _static_routecollectionfactory(self, *args, **kwargs) -> RouteCollection:
1798
+ return self.getview(GET) # type: ignore
1758
1799
 
1759
1800
 
1760
1801
  def register_converter(name, registry=ExtensiblePattern):
@@ -1775,3 +1816,24 @@ def register_converter(name, registry=ExtensiblePattern):
1775
1816
  return cls
1776
1817
 
1777
1818
  return register_converter
1819
+
1820
+
1821
+ def _has_request_parameter(func: Callable[..., Any]) -> bool:
1822
+ """
1823
+ Return True if the given function has an initial parameter of type Request
1824
+ """
1825
+
1826
+ def _is_request_annotation(a: Any) -> bool:
1827
+ if isinstance(a, type) and issubclass(a, Request):
1828
+ return True
1829
+
1830
+ origin = t.get_origin(a)
1831
+ if origin is t.Union and any(
1832
+ _is_request_annotation(arg) for arg in t.get_args(a)
1833
+ ):
1834
+ return True
1835
+ return False
1836
+
1837
+ sig = inspect.signature(func)
1838
+ firstparam = next(iter(sig.parameters.values()), None)
1839
+ 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):