fresco 3.0.0__tar.gz → 3.9.0__tar.gz
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-3.0.0 → fresco-3.9.0}/CHANGELOG.rst +95 -0
- {fresco-3.0.0/fresco.egg-info → fresco-3.9.0}/PKG-INFO +8 -12
- fresco-3.9.0/fresco/__init__.py +127 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/cookie.py +0 -1
- {fresco-3.0.0 → fresco-3.9.0}/fresco/core.py +83 -48
- {fresco-3.0.0 → fresco-3.9.0}/fresco/decorators.py +7 -6
- fresco-3.9.0/fresco/defaults.py +1 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/middleware.py +46 -26
- {fresco-3.0.0 → fresco-3.9.0}/fresco/multidict.py +67 -72
- fresco-3.9.0/fresco/options.py +536 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/request.py +200 -70
- {fresco-3.0.0 → fresco-3.9.0}/fresco/requestcontext.py +19 -9
- {fresco-3.0.0 → fresco-3.9.0}/fresco/response.py +114 -159
- {fresco-3.0.0 → fresco-3.9.0}/fresco/routeargs.py +23 -9
- {fresco-3.0.0 → fresco-3.9.0}/fresco/routing.py +247 -99
- {fresco-3.0.0 → fresco-3.9.0}/fresco/static.py +1 -1
- {fresco-3.0.0 → fresco-3.9.0}/fresco/subrequests.py +67 -54
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/fixtures.py +6 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_cookie.py +1 -3
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_core.py +30 -56
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_decorators.py +1 -3
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_middleware.py +0 -4
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_multidict.py +3 -5
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_options.py +137 -10
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_request.py +40 -22
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_requestcontext.py +0 -1
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_response.py +21 -6
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_routeargs.py +0 -5
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_routing.py +149 -51
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_static.py +2 -3
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_subrequests.py +53 -4
- fresco-3.9.0/fresco/tests/util/__init__.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/util/test_http.py +32 -46
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/util/test_urls.py +25 -9
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/util/test_wsgi.py +0 -2
- fresco-3.9.0/fresco/types.py +32 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/cache.py +2 -1
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/common.py +1 -3
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/contentencodings.py +3 -2
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/http.py +164 -105
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/io.py +3 -4
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/urls.py +52 -18
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/wsgi.py +26 -16
- {fresco-3.0.0 → fresco-3.9.0/fresco.egg-info}/PKG-INFO +8 -12
- {fresco-3.0.0 → fresco-3.9.0}/fresco.egg-info/SOURCES.txt +4 -3
- fresco-3.9.0/pyproject.toml +47 -0
- fresco-3.9.0/setup.cfg +4 -0
- fresco-3.0.0/fresco/__init__.py +0 -27
- fresco-3.0.0/fresco/options.py +0 -300
- fresco-3.0.0/fresco/typing.py +0 -11
- fresco-3.0.0/setup.cfg +0 -31
- fresco-3.0.0/setup.py +0 -17
- {fresco-3.0.0 → fresco-3.9.0}/LICENSE.txt +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/MANIFEST.in +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/README.rst +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/exceptions.py +0 -0
- /fresco-3.0.0/fresco/tests/__init__.py → /fresco-3.9.0/fresco/py.typed +0 -0
- {fresco-3.0.0/fresco/tests/util → fresco-3.9.0/fresco/tests}/__init__.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/test_exceptions.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/util/form_data.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/util/test_common.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/tests/util/test_security.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/__init__.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/file.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/object.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/security.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco/util/textproc.py +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco.egg-info/dependency_links.txt +0 -0
- {fresco-3.0.0 → fresco-3.9.0}/fresco.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,101 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
3.9.0 (released 2026-02-02)
|
|
5
|
+
---------------------------
|
|
6
|
+
|
|
7
|
+
- Bugfix: fix request.get and request.getint type annotations
|
|
8
|
+
- Allow multiple methods to be routed using kwargs, eg ``Route(GET_POST=my_view)``
|
|
9
|
+
- Add support for Python 3.13, 3.14 and drop support for Python 3.9
|
|
10
|
+
|
|
11
|
+
3.8.0 (released 2025-07-17)
|
|
12
|
+
---------------------------
|
|
13
|
+
|
|
14
|
+
- Add Response.bad_request ``message`` argument
|
|
15
|
+
- Add warnings when loading option files creates undefined keys
|
|
16
|
+
|
|
17
|
+
3.7.0 (released 2025-05-05)
|
|
18
|
+
---------------------------
|
|
19
|
+
|
|
20
|
+
- Add a ``fresco.util.url.add_query`` function
|
|
21
|
+
|
|
22
|
+
3.6.0 (released 2025-05-03)
|
|
23
|
+
---------------------------
|
|
24
|
+
|
|
25
|
+
- Replace ``fresco.DEFAULT_CHARSET`` with ``fresco.defaults.DEFAULT_CHARSET``
|
|
26
|
+
- Add a ``required`` argument to Request.get
|
|
27
|
+
- Add new request.get* functions: ``getfloat``, ``getbool`` and ``getdecimal``
|
|
28
|
+
|
|
29
|
+
3.5.0 (released 2025-02-25)
|
|
30
|
+
---------------------------
|
|
31
|
+
|
|
32
|
+
- Refactor middleware.XForwarded to clean up error tracebacks and add a new ``force_https`` option
|
|
33
|
+
- Add new ``fresco.options.override_options`` function to aid testing
|
|
34
|
+
- Automatically add a ``request`` argument where the routed function's first
|
|
35
|
+
positional parameter is annotated as type ``fresco.request.Request``.
|
|
36
|
+
view
|
|
37
|
+
|
|
38
|
+
3.4.0 (released 2024-10-16)
|
|
39
|
+
---------------------------
|
|
40
|
+
|
|
41
|
+
- Add new ``ALL`` shortcut method, allowing all HTTP methods to be routed to a
|
|
42
|
+
view
|
|
43
|
+
|
|
44
|
+
3.3.4 (released 2024-08-06)
|
|
45
|
+
---------------------------
|
|
46
|
+
|
|
47
|
+
- Bugfix: ensure option files are loaded in a well defined order
|
|
48
|
+
|
|
49
|
+
3.3.3 (released 2024-05-01)
|
|
50
|
+
---------------------------
|
|
51
|
+
|
|
52
|
+
- Bugfix: request.now returns the correct time on non-UTC systems
|
|
53
|
+
- Add support for Python 3.12 and drop support for 3.8
|
|
54
|
+
|
|
55
|
+
3.3.2 (released 2023-05-31)
|
|
56
|
+
---------------------------
|
|
57
|
+
|
|
58
|
+
- Bugfix: a fresco app routed through route_wsgi now receives a fresh request
|
|
59
|
+
and environ dict, isolated from the containing app.
|
|
60
|
+
|
|
61
|
+
3.3.1 (released 2023-05-18)
|
|
62
|
+
---------------------------
|
|
63
|
+
|
|
64
|
+
- The ``route_wsgi`` and ``route_all`` methods now only match at a
|
|
65
|
+
path separator boundary (``'/'``).
|
|
66
|
+
- Bugfix: subrequest now works with views routed via ``RRoute`` (and so expect
|
|
67
|
+
an initial ``request`` argument)
|
|
68
|
+
|
|
69
|
+
3.3.0 (released 2023-05-02)
|
|
70
|
+
----------------------------
|
|
71
|
+
|
|
72
|
+
- Options: fresco.options.Options can now take a list of paths as its first argument
|
|
73
|
+
- Options: tags can now be specified with environment variable substitutions
|
|
74
|
+
(eg "{FOO}" would load files tagged with the current value of the 'FOO'
|
|
75
|
+
environment variable)
|
|
76
|
+
- Options: add ``fresco.options.list_from_str`` and ``fresco.options.dict_from_options``
|
|
77
|
+
|
|
78
|
+
3.2.0 (released 2023-04-16)
|
|
79
|
+
---------------------------
|
|
80
|
+
|
|
81
|
+
- Bugfix: fixed cases where the close methods of WSGI content iterators were
|
|
82
|
+
not called, notably when using subrequests
|
|
83
|
+
- Bugfix: fix ``ResourceWarnings`` caused by unclosed temporary files
|
|
84
|
+
- Added ``route_class`` argument to ``RouteCollection.route``
|
|
85
|
+
- Modified ``RouteCollection.route_wsgi`` to allow it to take a string path to
|
|
86
|
+
the WSGI callable instead of the callable itself, making its signature more
|
|
87
|
+
consistent with ``RouteCollection.route``
|
|
88
|
+
- Added ``fresco.process_request_once``
|
|
89
|
+
- ``request.make_url`` and ``fresco.util.url.make_query`` now drop items from
|
|
90
|
+
the generated query string if the value is ``None``
|
|
91
|
+
|
|
92
|
+
3.1.0 (released 2022-05-04)
|
|
93
|
+
---------------------------
|
|
94
|
+
|
|
95
|
+
- Bugfix: passing URL paths to subrequests no longer raises an exception
|
|
96
|
+
- Added an ``_env`` argument to ``fresco.subrequest.subrequest`` to allow custom
|
|
97
|
+
WSGI environ keys to be passed to the subrequest
|
|
98
|
+
|
|
4
99
|
3.0.0 (released 2022-05-02)
|
|
5
100
|
---------------------------
|
|
6
101
|
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: fresco
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.0
|
|
4
4
|
Summary: A Web/WSGI micro-framework
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Keywords: wsgi web www framework
|
|
10
|
-
Platform: UNKNOWN
|
|
5
|
+
Author-email: Oliver Cope <oliver@redgecko.org>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://ollycope.com/software/fresco/latest/
|
|
8
|
+
Keywords: wsgi,web,www,framework
|
|
11
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
10
|
Classifier: Environment :: Web Environment
|
|
13
11
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
12
|
Classifier: Operating System :: OS Independent
|
|
16
13
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
18
14
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
19
15
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
20
17
|
License-File: LICENSE.txt
|
|
18
|
+
Dynamic: license-file
|
|
21
19
|
|
|
22
20
|
Fresco, a web micro-framework for Python
|
|
23
21
|
========================================
|
|
@@ -46,5 +44,3 @@ Read the
|
|
|
46
44
|
<https://ollycope.com/software/fresco/latest/>`_ for
|
|
47
45
|
more about the framework, or
|
|
48
46
|
visit the `source repo <https://sr.ht/~olly/fresco/>`_.
|
|
49
|
-
|
|
50
|
-
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Copyright 2015 Oliver Cope
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
from fresco.request import Request
|
|
16
|
+
from fresco.request import currentrequest
|
|
17
|
+
from fresco.requestcontext import context
|
|
18
|
+
from fresco.response import Response
|
|
19
|
+
from fresco.core import FrescoApp
|
|
20
|
+
from fresco.core import urlfor
|
|
21
|
+
from fresco.defaults import DEFAULT_CHARSET
|
|
22
|
+
from fresco.options import Options
|
|
23
|
+
from fresco.routing import Route
|
|
24
|
+
from fresco.routing import RRoute
|
|
25
|
+
from fresco.routing import RouteCollection
|
|
26
|
+
from fresco.routing import DelegateRoute
|
|
27
|
+
from fresco.routing import routefor
|
|
28
|
+
from fresco.routing import ALL_METHODS
|
|
29
|
+
from fresco.routing import GET
|
|
30
|
+
from fresco.routing import HEAD
|
|
31
|
+
from fresco.routing import POST
|
|
32
|
+
from fresco.routing import PUT
|
|
33
|
+
from fresco.routing import DELETE
|
|
34
|
+
from fresco.routing import OPTIONS
|
|
35
|
+
from fresco.routing import TRACE
|
|
36
|
+
from fresco.routing import CONNECT
|
|
37
|
+
from fresco.routing import VERSION_CONTROL
|
|
38
|
+
from fresco.routing import REPORT
|
|
39
|
+
from fresco.routing import CHECKOUT
|
|
40
|
+
from fresco.routing import CHECKIN
|
|
41
|
+
from fresco.routing import UNCHECKOUT
|
|
42
|
+
from fresco.routing import MKWORKSPACE
|
|
43
|
+
from fresco.routing import UPDATE
|
|
44
|
+
from fresco.routing import LABEL
|
|
45
|
+
from fresco.routing import MERGE
|
|
46
|
+
from fresco.routing import BASELINE_CONTROL
|
|
47
|
+
from fresco.routing import MKACTIVITY
|
|
48
|
+
from fresco.routing import ORDERPATCH
|
|
49
|
+
from fresco.routing import ACL
|
|
50
|
+
from fresco.routing import SEARCH
|
|
51
|
+
from fresco.routing import PATCH
|
|
52
|
+
from fresco.routeargs import routearg
|
|
53
|
+
from fresco.routeargs import FormArg
|
|
54
|
+
from fresco.routeargs import PostArg
|
|
55
|
+
from fresco.routeargs import QueryArg
|
|
56
|
+
from fresco.routeargs import GetArg
|
|
57
|
+
from fresco.routeargs import CookieArg
|
|
58
|
+
from fresco.routeargs import SessionArg
|
|
59
|
+
from fresco.routeargs import RequestObject
|
|
60
|
+
from fresco.routeargs import FormData
|
|
61
|
+
from fresco.routeargs import PostData
|
|
62
|
+
from fresco.routeargs import QueryData
|
|
63
|
+
from fresco.routeargs import GetData
|
|
64
|
+
from fresco.middleware import XForwarded
|
|
65
|
+
from fresco.subrequests import subrequest
|
|
66
|
+
from fresco.subrequests import subrequest_bytes
|
|
67
|
+
from fresco.subrequests import subrequest_raw
|
|
68
|
+
from fresco.util.common import object_or_404
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__version__ = "3.9.0"
|
|
72
|
+
__all__ = [
|
|
73
|
+
"Request",
|
|
74
|
+
"currentrequest",
|
|
75
|
+
"context",
|
|
76
|
+
"Response",
|
|
77
|
+
"FrescoApp",
|
|
78
|
+
"urlfor",
|
|
79
|
+
"ALL_METHODS",
|
|
80
|
+
"DEFAULT_CHARSET",
|
|
81
|
+
"GET",
|
|
82
|
+
"HEAD",
|
|
83
|
+
"POST",
|
|
84
|
+
"PUT",
|
|
85
|
+
"DELETE",
|
|
86
|
+
"OPTIONS",
|
|
87
|
+
"TRACE",
|
|
88
|
+
"CONNECT",
|
|
89
|
+
"VERSION_CONTROL",
|
|
90
|
+
"REPORT",
|
|
91
|
+
"CHECKOUT",
|
|
92
|
+
"CHECKIN",
|
|
93
|
+
"UNCHECKOUT",
|
|
94
|
+
"MKWORKSPACE",
|
|
95
|
+
"UPDATE",
|
|
96
|
+
"LABEL",
|
|
97
|
+
"MERGE",
|
|
98
|
+
"BASELINE_CONTROL",
|
|
99
|
+
"MKACTIVITY",
|
|
100
|
+
"ORDERPATCH",
|
|
101
|
+
"ACL",
|
|
102
|
+
"SEARCH",
|
|
103
|
+
"PATCH",
|
|
104
|
+
"DelegateRoute",
|
|
105
|
+
"Options",
|
|
106
|
+
"Route",
|
|
107
|
+
"RouteCollection",
|
|
108
|
+
"RRoute",
|
|
109
|
+
"routearg",
|
|
110
|
+
"FormArg",
|
|
111
|
+
"PostArg",
|
|
112
|
+
"QueryArg",
|
|
113
|
+
"GetArg",
|
|
114
|
+
"CookieArg",
|
|
115
|
+
"SessionArg",
|
|
116
|
+
"RequestObject",
|
|
117
|
+
"FormData",
|
|
118
|
+
"PostData",
|
|
119
|
+
"QueryData",
|
|
120
|
+
"GetData",
|
|
121
|
+
"routefor",
|
|
122
|
+
"XForwarded",
|
|
123
|
+
"subrequest",
|
|
124
|
+
"subrequest_bytes",
|
|
125
|
+
"subrequest_raw",
|
|
126
|
+
"object_or_404",
|
|
127
|
+
]
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
15
|
from functools import partial
|
|
16
|
+
from functools import wraps
|
|
16
17
|
from typing import Callable
|
|
17
18
|
from typing import Dict
|
|
18
19
|
from typing import List
|
|
@@ -20,6 +21,7 @@ from typing import Tuple
|
|
|
20
21
|
from typing import Type
|
|
21
22
|
from typing import Set
|
|
22
23
|
from typing import Union
|
|
24
|
+
import typing as t
|
|
23
25
|
import contextlib
|
|
24
26
|
import logging
|
|
25
27
|
import sys
|
|
@@ -31,7 +33,10 @@ from fresco.util.http import encode_multipart
|
|
|
31
33
|
from fresco.util.urls import normpath, make_query
|
|
32
34
|
from fresco.util.common import fq_path
|
|
33
35
|
from fresco.util.wsgi import make_environ
|
|
34
|
-
from fresco.
|
|
36
|
+
from fresco.types import WSGIApplication
|
|
37
|
+
from fresco.types import HeaderList
|
|
38
|
+
from fresco.types import OptionalExcInfo
|
|
39
|
+
from fresco.types import WriteCallable
|
|
35
40
|
|
|
36
41
|
from fresco.exceptions import ResponseException
|
|
37
42
|
from fresco.requestcontext import context
|
|
@@ -44,10 +49,12 @@ from fresco.routing import (
|
|
|
44
49
|
)
|
|
45
50
|
from fresco.options import Options
|
|
46
51
|
|
|
47
|
-
__all__ = ("FrescoApp", "urlfor")
|
|
52
|
+
__all__ = ("FrescoApp", "urlfor", "context")
|
|
48
53
|
|
|
49
54
|
logger = logging.getLogger(__name__)
|
|
50
55
|
|
|
56
|
+
ExcInfo = tuple[t.Type[BaseException], BaseException, types.TracebackType]
|
|
57
|
+
|
|
51
58
|
|
|
52
59
|
class FrescoApp(RouteCollection):
|
|
53
60
|
"""\
|
|
@@ -64,7 +71,6 @@ class FrescoApp(RouteCollection):
|
|
|
64
71
|
request_class = Request
|
|
65
72
|
|
|
66
73
|
def __init__(self, *args, **kwargs):
|
|
67
|
-
|
|
68
74
|
views = kwargs.pop("views", None)
|
|
69
75
|
path = kwargs.pop("path", None)
|
|
70
76
|
super(FrescoApp, self).__init__(*args, **kwargs)
|
|
@@ -114,7 +120,9 @@ class FrescoApp(RouteCollection):
|
|
|
114
120
|
#: If a function returns a value (other than ``None``),
|
|
115
121
|
#: this value will be
|
|
116
122
|
#: returned as the response instead of calling the scheduled view.
|
|
117
|
-
self.process_http_error_response_handlers:
|
|
123
|
+
self.process_http_error_response_handlers: list[
|
|
124
|
+
tuple[t.Optional[int], Callable[[Request, Response], t.Optional[Response]]]
|
|
125
|
+
] = []
|
|
118
126
|
|
|
119
127
|
#: Functions to be called if an exception is raised during a view
|
|
120
128
|
#: Each function will be passed ``request, exc_info``.
|
|
@@ -122,7 +130,9 @@ class FrescoApp(RouteCollection):
|
|
|
122
130
|
#: this value will be
|
|
123
131
|
#: returned as the response and the error will not be propagated.
|
|
124
132
|
#: If all exception handlers return None then the error will be raised
|
|
125
|
-
self.process_exception_handlers:
|
|
133
|
+
self.process_exception_handlers: list[
|
|
134
|
+
tuple[Type[Exception], Callable[[Request, ExcInfo], Union[Response, None]]]
|
|
135
|
+
] = []
|
|
126
136
|
|
|
127
137
|
#: Functions to be called at the end of request processing,
|
|
128
138
|
#: after all content has been output.
|
|
@@ -150,12 +160,12 @@ class FrescoApp(RouteCollection):
|
|
|
150
160
|
|
|
151
161
|
def get_response(
|
|
152
162
|
self,
|
|
153
|
-
request,
|
|
154
|
-
path,
|
|
155
|
-
method,
|
|
163
|
+
request: Request,
|
|
164
|
+
path: str,
|
|
165
|
+
method: str,
|
|
156
166
|
currentcontext=context.currentcontext,
|
|
157
167
|
normpath=normpath,
|
|
158
|
-
):
|
|
168
|
+
) -> Response:
|
|
159
169
|
ctx = currentcontext()
|
|
160
170
|
ctx["app"] = self
|
|
161
171
|
environ = request.environ
|
|
@@ -177,19 +187,19 @@ class FrescoApp(RouteCollection):
|
|
|
177
187
|
|
|
178
188
|
try:
|
|
179
189
|
for traversal in self.get_route_traversals(path, method, request):
|
|
190
|
+
route = traversal.route
|
|
180
191
|
try:
|
|
181
|
-
route = traversal.route
|
|
182
192
|
environ["wsgiorg.routing_args"] = (
|
|
183
193
|
traversal.args,
|
|
184
194
|
traversal.kwargs,
|
|
185
195
|
)
|
|
186
|
-
view =
|
|
196
|
+
view = traversal.view
|
|
187
197
|
ctx["view_self"] = getattr(view, "__self__", None)
|
|
188
198
|
ctx["route_traversal"] = traversal
|
|
189
199
|
if self.logger:
|
|
190
200
|
self.logger.info(
|
|
191
201
|
"matched route: %s %r => %r",
|
|
192
|
-
|
|
202
|
+
method,
|
|
193
203
|
path,
|
|
194
204
|
fq_path(view),
|
|
195
205
|
)
|
|
@@ -258,12 +268,12 @@ class FrescoApp(RouteCollection):
|
|
|
258
268
|
|
|
259
269
|
# Is the URL just missing a trailing '/'?
|
|
260
270
|
if not path or path[-1] != "/":
|
|
261
|
-
|
|
271
|
+
if self.get_methods(request, path + "/"):
|
|
262
272
|
return Response.unrestricted_redirect_permanent(path + "/")
|
|
263
273
|
|
|
264
274
|
return Response.not_found()
|
|
265
275
|
|
|
266
|
-
def view(self, request=None) -> Response:
|
|
276
|
+
def view(self, request: t.Optional[Request] = None) -> Response:
|
|
267
277
|
request = request or context.request
|
|
268
278
|
try:
|
|
269
279
|
path = request.path_info
|
|
@@ -271,9 +281,7 @@ class FrescoApp(RouteCollection):
|
|
|
271
281
|
response = e.response
|
|
272
282
|
else:
|
|
273
283
|
response = self.get_response(
|
|
274
|
-
request,
|
|
275
|
-
path,
|
|
276
|
-
request.method
|
|
284
|
+
request, path, request.environ["REQUEST_METHOD"]
|
|
277
285
|
)
|
|
278
286
|
|
|
279
287
|
for f in self.process_response_handlers:
|
|
@@ -290,7 +298,9 @@ class FrescoApp(RouteCollection):
|
|
|
290
298
|
|
|
291
299
|
return response
|
|
292
300
|
|
|
293
|
-
def handle_http_error_response(
|
|
301
|
+
def handle_http_error_response(
|
|
302
|
+
self, request: Request, response: Response
|
|
303
|
+
) -> Response:
|
|
294
304
|
"""
|
|
295
305
|
Call any process_http_error_response handlers and return the
|
|
296
306
|
(potentially modified) response object.
|
|
@@ -306,8 +316,8 @@ class FrescoApp(RouteCollection):
|
|
|
306
316
|
self.log_exception(request)
|
|
307
317
|
return response
|
|
308
318
|
|
|
309
|
-
def get_methods(self, request, path):
|
|
310
|
-
"""
|
|
319
|
+
def get_methods(self, request: Request, path: str) -> Set[str]:
|
|
320
|
+
"""
|
|
311
321
|
Return the HTTP methods valid in routes to the given path
|
|
312
322
|
"""
|
|
313
323
|
methods: Set[str] = set()
|
|
@@ -325,13 +335,7 @@ class FrescoApp(RouteCollection):
|
|
|
325
335
|
exc_info=exc_info,
|
|
326
336
|
)
|
|
327
337
|
|
|
328
|
-
def handle_exception(
|
|
329
|
-
self, request, allow_reraise=True
|
|
330
|
-
) -> Union[
|
|
331
|
-
Response,
|
|
332
|
-
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
|
333
|
-
]:
|
|
334
|
-
|
|
338
|
+
def handle_exception(self, request, allow_reraise=True) -> Response:
|
|
335
339
|
exc_info = sys.exc_info()
|
|
336
340
|
if exc_info[0] is None:
|
|
337
341
|
raise AssertionError(
|
|
@@ -348,10 +352,7 @@ class FrescoApp(RouteCollection):
|
|
|
348
352
|
# server handle it
|
|
349
353
|
if allow_reraise and not have_error_handlers:
|
|
350
354
|
raise exc_info[1].with_traceback(exc_info[2]) # type: ignore
|
|
351
|
-
response:
|
|
352
|
-
Response,
|
|
353
|
-
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
|
354
|
-
] = Response.internal_server_error()
|
|
355
|
+
response: Response = Response.internal_server_error()
|
|
355
356
|
|
|
356
357
|
if not self.process_exception_handlers:
|
|
357
358
|
self.log_exception(request, exc_info)
|
|
@@ -408,7 +409,7 @@ class FrescoApp(RouteCollection):
|
|
|
408
409
|
self.reset_wsgi_app()
|
|
409
410
|
self._middleware.insert(position, (middleware, args, kwargs))
|
|
410
411
|
|
|
411
|
-
def make_wsgi_app(self, wsgi_app=None, use_middleware=True) ->
|
|
412
|
+
def make_wsgi_app(self, wsgi_app=None, use_middleware=True) -> WSGIApplication:
|
|
412
413
|
"""
|
|
413
414
|
Return a WSGI (PEP-3333) compliant application that drives this
|
|
414
415
|
FrescoApp object.
|
|
@@ -421,7 +422,7 @@ class FrescoApp(RouteCollection):
|
|
|
421
422
|
"""
|
|
422
423
|
if wsgi_app is None:
|
|
423
424
|
|
|
424
|
-
def
|
|
425
|
+
def _wsgi_app(
|
|
425
426
|
environ,
|
|
426
427
|
start_response,
|
|
427
428
|
view=self.view,
|
|
@@ -430,15 +431,18 @@ class FrescoApp(RouteCollection):
|
|
|
430
431
|
request = request_class(environ)
|
|
431
432
|
return view(request)(environ, start_response)
|
|
432
433
|
|
|
434
|
+
else:
|
|
435
|
+
_wsgi_app = wsgi_app
|
|
436
|
+
|
|
433
437
|
if use_middleware:
|
|
434
438
|
for m, m_args, m_kwargs in self._middleware:
|
|
435
|
-
|
|
439
|
+
_wsgi_app = m(_wsgi_app, *m_args, **m_kwargs)
|
|
436
440
|
|
|
437
441
|
def fresco_wsgi_app(
|
|
438
442
|
environ,
|
|
439
443
|
start_response,
|
|
440
444
|
frescoapp=self,
|
|
441
|
-
wsgi_app=
|
|
445
|
+
wsgi_app=_wsgi_app,
|
|
442
446
|
request_class=self.request_class,
|
|
443
447
|
process_teardown_handlers=self.process_teardown_handlers,
|
|
444
448
|
call_process_teardown_handlers=self.call_process_teardown_handlers,
|
|
@@ -450,8 +454,7 @@ class FrescoApp(RouteCollection):
|
|
|
450
454
|
iterator = None
|
|
451
455
|
try:
|
|
452
456
|
iterator = wsgi_app(environ, start_response)
|
|
453
|
-
|
|
454
|
-
yield item
|
|
457
|
+
yield from iterator
|
|
455
458
|
except Exception:
|
|
456
459
|
exc_info = sys.exc_info()
|
|
457
460
|
try:
|
|
@@ -464,14 +467,15 @@ class FrescoApp(RouteCollection):
|
|
|
464
467
|
def exc_start_response(s, h, exc_info=exc_info):
|
|
465
468
|
return start_response(s, h, exc_info)
|
|
466
469
|
|
|
467
|
-
|
|
468
|
-
yield item
|
|
470
|
+
yield from response(environ, exc_start_response)
|
|
469
471
|
finally:
|
|
470
472
|
del exc_info
|
|
471
473
|
finally:
|
|
472
474
|
try:
|
|
473
475
|
if process_teardown_handlers:
|
|
474
476
|
call_process_teardown_handlers(request)
|
|
477
|
+
for item in request.teardown_handlers:
|
|
478
|
+
item()
|
|
475
479
|
finally:
|
|
476
480
|
try:
|
|
477
481
|
close = getattr(iterator, "close", None)
|
|
@@ -562,6 +566,27 @@ class FrescoApp(RouteCollection):
|
|
|
562
566
|
except Exception:
|
|
563
567
|
self.log_exception(request)
|
|
564
568
|
|
|
569
|
+
def process_request_once(
|
|
570
|
+
self, func: Callable[[Request], t.Optional[Response]]
|
|
571
|
+
) -> Callable[[Request], t.Optional[Response]]:
|
|
572
|
+
"""
|
|
573
|
+
Register a ``process_request`` hook function that is called only once
|
|
574
|
+
|
|
575
|
+
When running fresco with multiple worker threads/processes the hook
|
|
576
|
+
function will be called at most once per worker.
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
@self.process_request
|
|
580
|
+
@wraps(func)
|
|
581
|
+
def process_request_once(request: Request) -> t.Optional[Response]:
|
|
582
|
+
try:
|
|
583
|
+
self.process_request_handlers.remove(process_request_once)
|
|
584
|
+
except ValueError:
|
|
585
|
+
return None
|
|
586
|
+
return func(request)
|
|
587
|
+
|
|
588
|
+
return func
|
|
589
|
+
|
|
565
590
|
def process_request(self, func):
|
|
566
591
|
"""
|
|
567
592
|
Register a ``process_request`` hook function
|
|
@@ -583,7 +608,11 @@ class FrescoApp(RouteCollection):
|
|
|
583
608
|
self.process_response_handlers.append(func)
|
|
584
609
|
return func
|
|
585
610
|
|
|
586
|
-
def process_exception(
|
|
611
|
+
def process_exception(
|
|
612
|
+
self,
|
|
613
|
+
func: Callable[[Request, ExcInfo], Union[Response, None]],
|
|
614
|
+
exc_type: Type[Exception] = Exception,
|
|
615
|
+
):
|
|
587
616
|
"""
|
|
588
617
|
Register a ``process_exception`` hook function
|
|
589
618
|
"""
|
|
@@ -649,33 +678,39 @@ class FrescoApp(RouteCollection):
|
|
|
649
678
|
start_response("200 OK", [])
|
|
650
679
|
yield b""
|
|
651
680
|
|
|
652
|
-
def fake_start_response(
|
|
681
|
+
def fake_start_response(
|
|
682
|
+
status: str, headers: HeaderList, exc_info: OptionalExcInfo = None
|
|
683
|
+
) -> WriteCallable:
|
|
653
684
|
return lambda s: None
|
|
654
685
|
|
|
655
686
|
environ = make_environ(url, environ, wsgi_input, **kwargs)
|
|
656
687
|
app = self.make_wsgi_app(wsgi_app=fake_app, use_middleware=middleware)
|
|
657
688
|
result = app(environ, fake_start_response)
|
|
658
|
-
close = getattr(result, "close",
|
|
689
|
+
close = getattr(result, "close", None)
|
|
659
690
|
content_iterator = iter(result)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
691
|
+
try:
|
|
692
|
+
next(content_iterator, None)
|
|
693
|
+
yield context
|
|
694
|
+
list(content_iterator)
|
|
695
|
+
finally:
|
|
696
|
+
if close is not None:
|
|
697
|
+
close()
|
|
664
698
|
|
|
665
699
|
def requestcontext_with_payload(
|
|
666
700
|
self, url="/", data=None, environ=None, files=None, multipart=False, **kwargs
|
|
667
701
|
):
|
|
668
|
-
|
|
669
702
|
if files:
|
|
670
703
|
multipart = True
|
|
671
704
|
|
|
672
705
|
if multipart:
|
|
673
706
|
wsgi_input, headers = encode_multipart(data, files)
|
|
674
707
|
kwargs.update(headers)
|
|
675
|
-
elif
|
|
708
|
+
elif isinstance(data, t.BinaryIO):
|
|
676
709
|
wsgi_input = data.read()
|
|
677
710
|
elif isinstance(data, bytes):
|
|
678
711
|
wsgi_input = data
|
|
712
|
+
elif data is None:
|
|
713
|
+
wsgi_input = b""
|
|
679
714
|
else:
|
|
680
715
|
wsgi_input = make_query(data).encode("ascii")
|
|
681
716
|
|
|
@@ -14,20 +14,23 @@
|
|
|
14
14
|
#
|
|
15
15
|
import sys
|
|
16
16
|
from functools import wraps
|
|
17
|
+
import typing as t
|
|
17
18
|
|
|
18
19
|
from fresco import Response
|
|
19
20
|
|
|
20
21
|
_marker = object()
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
def onerror(
|
|
24
|
-
|
|
24
|
+
def onerror(
|
|
25
|
+
exceptions: t.Union[t.Type[Exception], tuple[t.Type[Exception], ...]], handler
|
|
26
|
+
) -> t.Callable:
|
|
27
|
+
"""
|
|
25
28
|
Return a decorator that can replace or update the return value of the
|
|
26
29
|
function if an exception is raised
|
|
27
30
|
"""
|
|
28
31
|
|
|
29
32
|
try:
|
|
30
|
-
if isinstance(exceptions,
|
|
33
|
+
if isinstance(exceptions, type):
|
|
31
34
|
exceptions = (exceptions,)
|
|
32
35
|
except TypeError:
|
|
33
36
|
pass
|
|
@@ -79,9 +82,7 @@ def json_response(
|
|
|
79
82
|
def json_response_decorator(func):
|
|
80
83
|
@wraps(func)
|
|
81
84
|
def json_response_decorated(*fa, **fkw):
|
|
82
|
-
return Response.json(
|
|
83
|
-
func(*fa, **fkw), indent, separators, **kwargs
|
|
84
|
-
)
|
|
85
|
+
return Response.json(func(*fa, **fkw), indent, separators, **kwargs)
|
|
85
86
|
|
|
86
87
|
return json_response_decorated
|
|
87
88
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT_CHARSET = "UTF-8"
|