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/response.py CHANGED
@@ -16,12 +16,14 @@
16
16
  The :class:`Response` class models the response from your application to a
17
17
  single request.
18
18
  """
19
+ from collections.abc import Iterable
19
20
  from datetime import datetime
20
21
  from itertools import chain
21
22
  from typing import Callable
22
23
  from typing import List
23
24
  from typing import Tuple
24
25
  from typing import Set
26
+ import typing as t
25
27
  import re
26
28
  import json as stdlib_json
27
29
 
@@ -275,7 +277,7 @@ def encoder(stream, charset):
275
277
 
276
278
 
277
279
  def make_header_name(name):
278
- """\
280
+ """
279
281
  Return a formatted header name from a python idenfier.
280
282
 
281
283
  Example usage::
@@ -331,6 +333,7 @@ class Response(object):
331
333
  """
332
334
 
333
335
  default_content_type = "text/html; charset=UTF-8"
336
+ content_iterator: Iterable[bytes]
334
337
 
335
338
  def __init__(
336
339
  self,
@@ -386,7 +389,7 @@ class Response(object):
386
389
  self.status = "200 OK"
387
390
  else:
388
391
  try:
389
- self.status = f"{status} {HTTP_STATUS_CODES[status]}"
392
+ self.status = f"{status} {HTTP_STATUS_CODES[status]}" # type: ignore
390
393
  except KeyError:
391
394
  self.status = str(status)
392
395
 
@@ -441,7 +444,7 @@ class Response(object):
441
444
  else:
442
445
  self.content_iterator = encoder(content, self.charset)
443
446
 
444
- def __call__(self, environ, start_response, exc_info=None):
447
+ def __call__(self, environ, start_response, exc_info=None) -> Iterable[bytes]:
445
448
  """
446
449
  WSGI callable. Calls ``start_response`` with assigned headers and
447
450
  returns an iterator over ``content``.
@@ -449,7 +452,7 @@ class Response(object):
449
452
  start_response(self.status, self.headers, exc_info)
450
453
  result = self.content_iterator
451
454
  if self.onclose:
452
- result = ClosingIterator(result, *self.onclose)
455
+ result = ClosingIterator[bytes](result, *self.onclose)
453
456
  return result
454
457
 
455
458
  def add_onclose(self, *funcs):
@@ -610,12 +613,12 @@ class Response(object):
610
613
  value,
611
614
  max_age=None,
612
615
  expires=None,
613
- path="/",
614
- secure=None,
615
- domain=None,
616
- comment=None,
617
- httponly=False,
618
- samesite="Lax",
616
+ path: str = "/",
617
+ secure: bool = False,
618
+ domain: t.Optional[str] = None,
619
+ comment: t.Optional[str] = None,
620
+ httponly: bool = False,
621
+ samesite: str = "Lax",
619
622
  ):
620
623
  """
621
624
  Return a new response object with the given cookie added.
@@ -674,8 +677,10 @@ class Response(object):
674
677
  newheaders.append((k, v))
675
678
  return self.replace(headers=newheaders + [("Vary", ", ".join(_vary_on))])
676
679
 
677
- def replace(self, content=None, status=None, headers=None, **kwheaders):
678
- """\
680
+ def replace(
681
+ self, content=None, status=None, headers=None, **kwheaders
682
+ ) -> "Response":
683
+ """
679
684
  Return a new response object with any of content, status or headers
680
685
  changed.
681
686
 
@@ -755,7 +760,7 @@ class Response(object):
755
760
  return default_charset
756
761
 
757
762
  @classmethod
758
- def not_found(cls, request=None):
763
+ def not_found(cls, request=None) -> "Response":
759
764
  """
760
765
  Return an HTTP not found response (404).
761
766
 
@@ -778,7 +783,7 @@ class Response(object):
778
783
  )
779
784
 
780
785
  @classmethod
781
- def error(cls, message="500 Internal Server Error"):
786
+ def error(cls, message="500 Internal Server Error") -> "Response":
782
787
  """
783
788
  Return an HTTP server error response (500).
784
789
 
@@ -800,7 +805,7 @@ class Response(object):
800
805
  )
801
806
 
802
807
  @classmethod
803
- def unauthorized(cls, authenticate):
808
+ def unauthorized(cls, authenticate) -> "Response":
804
809
  """
805
810
  Return an HTTP unauthorized response (401)
806
811
  """
@@ -815,7 +820,7 @@ class Response(object):
815
820
  )
816
821
 
817
822
  @classmethod
818
- def unauthorized_basic(cls, realm):
823
+ def unauthorized_basic(cls, realm) -> "Response":
819
824
  """
820
825
  Return an HTTP unauthorized response (401) with a WWW-Authenticate
821
826
  header set for HTTP Basic authentication..
@@ -823,7 +828,7 @@ class Response(object):
823
828
  return cls.unauthorized(authenticate=f'Basic realm="{realm}"')
824
829
 
825
830
  @classmethod
826
- def forbidden(cls, message="Sorry, access is denied"):
831
+ def forbidden(cls, message="Sorry, access is denied") -> "Response":
827
832
  """
828
833
  Return an HTTP forbidden response (403).
829
834
 
@@ -839,7 +844,9 @@ class Response(object):
839
844
  )
840
845
 
841
846
  @classmethod
842
- def bad_request(cls, request=None):
847
+ def bad_request(
848
+ cls, message="The server could not understand your request", request=None
849
+ ) -> "Response":
843
850
  """
844
851
  Return an HTTP bad request response.
845
852
 
@@ -853,16 +860,17 @@ class Response(object):
853
860
  return cls(
854
861
  status=STATUS_BAD_REQUEST,
855
862
  content=[
856
- "<html>"
857
- "<body>"
858
- "<h1>The server could not understand your request</h1>"
859
- "</body>"
860
- "</html>"
863
+ f"<html>"
864
+ f"<body>"
865
+ f"<h1>Bad request</h1>"
866
+ f"<p>{message}</p>"
867
+ f"</body>"
868
+ f"</html>"
861
869
  ],
862
870
  )
863
871
 
864
872
  @classmethod
865
- def length_required(cls, request=None):
873
+ def length_required(cls, request=None) -> "Response":
866
874
  """
867
875
  Return an HTTP Length Required response (411).
868
876
 
@@ -885,7 +893,7 @@ class Response(object):
885
893
  )
886
894
 
887
895
  @classmethod
888
- def payload_too_large(cls, request=None):
896
+ def payload_too_large(cls, request=None) -> "Response":
889
897
  """
890
898
  Return an HTTP Payload Too Large response (413)::
891
899
 
@@ -900,7 +908,7 @@ class Response(object):
900
908
  request_entity_too_large = payload_too_large
901
909
 
902
910
  @classmethod
903
- def method_not_allowed(cls, valid_methods):
911
+ def method_not_allowed(cls, valid_methods) -> "Response":
904
912
  """
905
913
  Return an HTTP method not allowed response (405)::
906
914
 
@@ -922,7 +930,7 @@ class Response(object):
922
930
  )
923
931
 
924
932
  @classmethod
925
- def internal_server_error(cls):
933
+ def internal_server_error(cls) -> "Response":
926
934
  """
927
935
  Return an HTTP internal server error response (500).
928
936
 
@@ -943,7 +951,7 @@ class Response(object):
943
951
  @classmethod
944
952
  def unrestricted_redirect(
945
953
  cls, location, request=None, status=STATUS_FOUND, **kwargs
946
- ):
954
+ ) -> "Response":
947
955
  """
948
956
  Return an HTTP redirect reponse (30x).
949
957
 
@@ -1001,7 +1009,7 @@ class Response(object):
1001
1009
  request = fresco.context.request
1002
1010
  location = request.resolve_url(location)
1003
1011
 
1004
- return Response(
1012
+ return cls(
1005
1013
  "<html><head></head><body>\n"
1006
1014
  "<h1>Page has moved</h1>\n"
1007
1015
  "<p><a href='%s'>%s</a></p>\n"
@@ -1011,8 +1019,8 @@ class Response(object):
1011
1019
  )
1012
1020
 
1013
1021
  @classmethod
1014
- def unrestricted_redirect_permanent(cls, *args, **kwargs):
1015
- """\
1022
+ def unrestricted_redirect_permanent(cls, *args, **kwargs) -> "Response":
1023
+ """
1016
1024
  Return an HTTP permanent redirect reponse.
1017
1025
 
1018
1026
  :param location: the URI of the new location. If relative this will be
@@ -1024,8 +1032,8 @@ class Response(object):
1024
1032
  return cls.unrestricted_redirect(*args, **kwargs)
1025
1033
 
1026
1034
  @classmethod
1027
- def unrestricted_redirect_temporary(cls, *args, **kwargs):
1028
- """\
1035
+ def unrestricted_redirect_temporary(cls, *args, **kwargs) -> "Response":
1036
+ """
1029
1037
  Return an HTTP permanent redirect reponse.
1030
1038
 
1031
1039
  :param location: the URI of the new location. If relative this will be
@@ -1045,7 +1053,7 @@ class Response(object):
1045
1053
  _is_safe_url=is_safe_url,
1046
1054
  allowed_hosts=frozenset(),
1047
1055
  **kwargs,
1048
- ):
1056
+ ) -> "Response":
1049
1057
  """
1050
1058
  Return an HTTP redirect reponse (30x). Will only redirect to the
1051
1059
  current host or hosts in the ``allowed_hosts`` list. A ``ValueError``
@@ -1072,19 +1080,22 @@ class Response(object):
1072
1080
  """
1073
1081
  # Create the fallback redirect response always so that
1074
1082
  # changes to route names don't result in latent bugs
1083
+ fallback_response = None
1075
1084
  if fallback:
1076
- fallback = Response.unrestricted_redirect(fallback, status=status, **kwargs)
1077
- if callable(url) or "//" not in url:
1085
+ fallback_response = cls.unrestricted_redirect(
1086
+ fallback, status=status, **kwargs
1087
+ )
1088
+ if url and (
1089
+ callable(url) or "//" not in url or _is_safe_url(url, allowed_hosts)
1090
+ ):
1078
1091
  return cls.unrestricted_redirect(url, status=status, **kwargs)
1079
1092
 
1080
- if url and _is_safe_url(url, allowed_hosts):
1081
- return cls.unrestricted_redirect(url, status=status, **kwargs)
1082
- if fallback:
1083
- return fallback
1093
+ if fallback_response:
1094
+ return fallback_response
1084
1095
  raise ValueError("Unsafe URL")
1085
1096
 
1086
1097
  @classmethod
1087
- def redirect_permanent(cls, *args, **kwargs):
1098
+ def redirect_permanent(cls, *args, **kwargs) -> "Response":
1088
1099
  """
1089
1100
  Return an HTTP permanent redirect reponse.
1090
1101
 
@@ -1097,7 +1108,7 @@ class Response(object):
1097
1108
  return cls.redirect(*args, **kwargs)
1098
1109
 
1099
1110
  @classmethod
1100
- def redirect_temporary(cls, *args, **kwargs):
1111
+ def redirect_temporary(cls, *args, **kwargs) -> "Response":
1101
1112
  """
1102
1113
  Return an HTTP permanent redirect reponse.
1103
1114
 
@@ -1110,7 +1121,7 @@ class Response(object):
1110
1121
  return cls.redirect(*args, **kwargs)
1111
1122
 
1112
1123
  @classmethod
1113
- def meta_refresh(cls, location, delay=1, request=None):
1124
+ def meta_refresh(cls, location, delay=1, **kwargs) -> "Response":
1114
1125
  """
1115
1126
  Return an HTML page containing a <meta http-equiv="refresh"> tag,
1116
1127
  causing the browser to redirect to the given location after ``delay``
@@ -1121,21 +1132,19 @@ class Response(object):
1121
1132
  request.
1122
1133
 
1123
1134
  """
1124
- if "://" not in location:
1125
- if request is None:
1126
- request = fresco.context.request
1127
- location = request.resolve_url(location)
1128
- return cls(
1129
- [
1130
- (
1131
- "<!DOCTYPE html>"
1132
- "<html>"
1133
- '<head><meta http-equiv="refresh" content="0; url={0}"></head>'
1134
- '<body><p><a href="{0}">Click here to continue</a></p></body>'
1135
- "</html>"
1136
- ).format(location)
1135
+ response = cls.redirect(location, **kwargs)
1136
+ location = response.get_header("location")
1137
+ return response.replace(
1138
+ status="200 OK",
1139
+ content=[
1140
+ f"<!DOCTYPE html>"
1141
+ f"<html>"
1142
+ f'<head><meta http-equiv="refresh" content="0; url={location}"></head>'
1143
+ f'<body><p><a href="{location}">Click here to continue</a></p></body>'
1144
+ f"</html>"
1137
1145
  ],
1138
1146
  content_type="text/html",
1147
+ location=None,
1139
1148
  )
1140
1149
 
1141
1150
  @classmethod
@@ -1149,7 +1158,7 @@ class Response(object):
1149
1158
  headers=None,
1150
1159
  dumps=stdlib_json.dumps,
1151
1160
  **kwargs,
1152
- ):
1161
+ ) -> "Response":
1153
1162
  """
1154
1163
  Return an ``application/json`` response with the given data
1155
1164
  JSON serialized
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(object):
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(object):
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=None,
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, list)
266
+ self.is_list = isinstance(converter, Sequence)
255
267
  if self.is_list:
256
- self.converter = lambda vs: [c(v) for c, v in zip(cycle(converter), vs)]
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: