fresco 3.3.1__tar.gz → 3.8.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.
Potentially problematic release.
This version of fresco might be problematic. Click here for more details.
- {fresco-3.3.1 → fresco-3.8.0}/CHANGELOG.rst +52 -2
- {fresco-3.3.1/fresco.egg-info → fresco-3.8.0}/PKG-INFO +8 -9
- {fresco-3.3.1 → fresco-3.8.0}/fresco/__init__.py +55 -56
- {fresco-3.3.1 → fresco-3.8.0}/fresco/core.py +39 -27
- {fresco-3.3.1 → fresco-3.8.0}/fresco/decorators.py +6 -3
- fresco-3.8.0/fresco/defaults.py +1 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/middleware.py +45 -24
- {fresco-3.3.1 → fresco-3.8.0}/fresco/multidict.py +35 -51
- {fresco-3.3.1 → fresco-3.8.0}/fresco/options.py +188 -62
- {fresco-3.3.1 → fresco-3.8.0}/fresco/request.py +156 -35
- {fresco-3.3.1 → fresco-3.8.0}/fresco/requestcontext.py +3 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/response.py +52 -41
- {fresco-3.3.1 → fresco-3.8.0}/fresco/routeargs.py +23 -9
- {fresco-3.3.1 → fresco-3.8.0}/fresco/routing.py +146 -73
- {fresco-3.3.1 → fresco-3.8.0}/fresco/static.py +1 -1
- {fresco-3.3.1 → fresco-3.8.0}/fresco/subrequests.py +3 -5
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_core.py +4 -4
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_multidict.py +2 -2
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_options.py +61 -12
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_request.py +21 -10
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_routing.py +119 -31
- fresco-3.8.0/fresco/tests/util/__init__.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/util/test_http.py +1 -3
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/util/test_urls.py +20 -0
- fresco-3.8.0/fresco/types.py +32 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/cache.py +2 -1
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/contentencodings.py +2 -1
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/http.py +67 -45
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/urls.py +44 -12
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/wsgi.py +16 -15
- {fresco-3.3.1 → fresco-3.8.0/fresco.egg-info}/PKG-INFO +8 -9
- {fresco-3.3.1 → fresco-3.8.0}/fresco.egg-info/SOURCES.txt +3 -3
- fresco-3.8.0/pyproject.toml +47 -0
- fresco-3.8.0/setup.cfg +4 -0
- fresco-3.3.1/fresco/types.py +0 -6
- fresco-3.3.1/fresco/typing.py +0 -11
- fresco-3.3.1/setup.cfg +0 -31
- fresco-3.3.1/setup.py +0 -17
- {fresco-3.3.1 → fresco-3.8.0}/LICENSE.txt +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/MANIFEST.in +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/README.rst +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/cookie.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/exceptions.py +0 -0
- /fresco-3.3.1/fresco/tests/__init__.py → /fresco-3.8.0/fresco/py.typed +0 -0
- {fresco-3.3.1/fresco/tests/util → fresco-3.8.0/fresco/tests}/__init__.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/fixtures.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_cookie.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_decorators.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_exceptions.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_middleware.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_requestcontext.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_response.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_routeargs.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_static.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/test_subrequests.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/util/form_data.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/util/test_common.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/util/test_security.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/tests/util/test_wsgi.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/__init__.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/common.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/file.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/io.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/object.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/security.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco/util/textproc.py +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco.egg-info/dependency_links.txt +0 -0
- {fresco-3.3.1 → fresco-3.8.0}/fresco.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
3.8.0 (released 2025-07-17)
|
|
5
|
+
---------------------------
|
|
6
|
+
|
|
7
|
+
- Add Response.bad_request ``message`` argument
|
|
8
|
+
- Add warnings when loading option files creates undefined keys
|
|
9
|
+
|
|
10
|
+
3.7.0 (released 2025-05-05)
|
|
11
|
+
---------------------------
|
|
12
|
+
|
|
13
|
+
- Add a ``fresco.util.url.add_query`` function
|
|
14
|
+
|
|
15
|
+
3.6.0 (released 2025-05-03)
|
|
16
|
+
---------------------------
|
|
17
|
+
|
|
18
|
+
- Replace ``fresco.DEFAULT_CHARSET`` with ``fresco.defaults.DEFAULT_CHARSET``
|
|
19
|
+
- Add a ``required`` argument to Request.get
|
|
20
|
+
- Add new request.get* functions: ``getfloat``, ``getbool`` and ``getdecimal``
|
|
21
|
+
|
|
22
|
+
3.5.0 (released 2025-02-25)
|
|
23
|
+
---------------------------
|
|
24
|
+
|
|
25
|
+
- Refactor middleware.XForwarded to clean up error tracebacks and add a new ``force_https`` option
|
|
26
|
+
- Add new ``fresco.options.override_options`` function to aid testing
|
|
27
|
+
- Automatically add a ``request`` argument where the routed function's first
|
|
28
|
+
positional parameter is annotated as type ``fresco.request.Request``.
|
|
29
|
+
view
|
|
30
|
+
|
|
31
|
+
3.4.0 (released 2024-10-16)
|
|
32
|
+
---------------------------
|
|
33
|
+
|
|
34
|
+
- Add new ``ALL`` shortcut method, allowing all HTTP methods to be routed to a
|
|
35
|
+
view
|
|
36
|
+
|
|
37
|
+
3.3.4 (released 2024-08-06)
|
|
38
|
+
---------------------------
|
|
39
|
+
|
|
40
|
+
- Bugfix: ensure option files are loaded in a well defined order
|
|
41
|
+
|
|
42
|
+
3.3.3 (released 2024-05-01)
|
|
43
|
+
---------------------------
|
|
44
|
+
|
|
45
|
+
- Bugfix: request.now returns the correct time on non-UTC systems
|
|
46
|
+
- Add support for Python 3.12 and drop support for 3.8
|
|
47
|
+
|
|
48
|
+
3.3.2 (released 2023-05-31)
|
|
49
|
+
---------------------------
|
|
50
|
+
|
|
51
|
+
- Bugfix: a fresco app routed through route_wsgi now receives a fresh request
|
|
52
|
+
and environ dict, isolated from the containing app.
|
|
53
|
+
|
|
4
54
|
3.3.1 (released 2023-05-18)
|
|
5
55
|
---------------------------
|
|
6
56
|
|
|
@@ -9,8 +59,8 @@ Changelog
|
|
|
9
59
|
- Bugfix: subrequest now works with views routed via ``RRoute`` (and so expect
|
|
10
60
|
an initial ``request`` argument)
|
|
11
61
|
|
|
12
|
-
3.3.0
|
|
13
|
-
|
|
62
|
+
3.3.0 (released 2023-05-02)
|
|
63
|
+
----------------------------
|
|
14
64
|
|
|
15
65
|
- Options: fresco.options.Options can now take a list of paths as its first argument
|
|
16
66
|
- Options: tags can now be specified with environment variable substitutions
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: fresco
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: A Web/WSGI micro-framework
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Keywords: wsgi web www framework
|
|
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
|
|
10
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
10
|
Classifier: Environment :: Web Environment
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
12
|
Classifier: Operating System :: OS Independent
|
|
15
13
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
17
14
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
18
15
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
19
17
|
License-File: LICENSE.txt
|
|
18
|
+
Dynamic: license-file
|
|
20
19
|
|
|
21
20
|
Fresco, a web micro-framework for Python
|
|
22
21
|
========================================
|
|
@@ -12,10 +12,63 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
|
-
|
|
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
|
|
16
69
|
|
|
17
|
-
DEFAULT_CHARSET = "UTF-8"
|
|
18
70
|
|
|
71
|
+
__version__ = "3.8.0"
|
|
19
72
|
__all__ = [
|
|
20
73
|
"Request",
|
|
21
74
|
"currentrequest",
|
|
@@ -72,57 +125,3 @@ __all__ = [
|
|
|
72
125
|
"subrequest_raw",
|
|
73
126
|
"object_or_404",
|
|
74
127
|
]
|
|
75
|
-
|
|
76
|
-
from fresco.request import Request
|
|
77
|
-
from fresco.request import currentrequest
|
|
78
|
-
from fresco.requestcontext import context
|
|
79
|
-
from fresco.response import Response
|
|
80
|
-
from fresco.core import FrescoApp
|
|
81
|
-
from fresco.core import urlfor
|
|
82
|
-
from fresco.options import Options
|
|
83
|
-
from fresco.routing import Route
|
|
84
|
-
from fresco.routing import RRoute
|
|
85
|
-
from fresco.routing import RouteCollection
|
|
86
|
-
from fresco.routing import DelegateRoute
|
|
87
|
-
from fresco.routing import routefor
|
|
88
|
-
from fresco.routing import ALL_METHODS
|
|
89
|
-
from fresco.routing import GET
|
|
90
|
-
from fresco.routing import HEAD
|
|
91
|
-
from fresco.routing import POST
|
|
92
|
-
from fresco.routing import PUT
|
|
93
|
-
from fresco.routing import DELETE
|
|
94
|
-
from fresco.routing import OPTIONS
|
|
95
|
-
from fresco.routing import TRACE
|
|
96
|
-
from fresco.routing import CONNECT
|
|
97
|
-
from fresco.routing import VERSION_CONTROL
|
|
98
|
-
from fresco.routing import REPORT
|
|
99
|
-
from fresco.routing import CHECKOUT
|
|
100
|
-
from fresco.routing import CHECKIN
|
|
101
|
-
from fresco.routing import UNCHECKOUT
|
|
102
|
-
from fresco.routing import MKWORKSPACE
|
|
103
|
-
from fresco.routing import UPDATE
|
|
104
|
-
from fresco.routing import LABEL
|
|
105
|
-
from fresco.routing import MERGE
|
|
106
|
-
from fresco.routing import BASELINE_CONTROL
|
|
107
|
-
from fresco.routing import MKACTIVITY
|
|
108
|
-
from fresco.routing import ORDERPATCH
|
|
109
|
-
from fresco.routing import ACL
|
|
110
|
-
from fresco.routing import SEARCH
|
|
111
|
-
from fresco.routing import PATCH
|
|
112
|
-
from fresco.routeargs import routearg
|
|
113
|
-
from fresco.routeargs import FormArg
|
|
114
|
-
from fresco.routeargs import PostArg
|
|
115
|
-
from fresco.routeargs import QueryArg
|
|
116
|
-
from fresco.routeargs import GetArg
|
|
117
|
-
from fresco.routeargs import CookieArg
|
|
118
|
-
from fresco.routeargs import SessionArg
|
|
119
|
-
from fresco.routeargs import RequestObject
|
|
120
|
-
from fresco.routeargs import FormData
|
|
121
|
-
from fresco.routeargs import PostData
|
|
122
|
-
from fresco.routeargs import QueryData
|
|
123
|
-
from fresco.routeargs import GetData
|
|
124
|
-
from fresco.middleware import XForwarded
|
|
125
|
-
from fresco.subrequests import subrequest
|
|
126
|
-
from fresco.subrequests import subrequest_bytes
|
|
127
|
-
from fresco.subrequests import subrequest_raw
|
|
128
|
-
from fresco.util.common import object_or_404
|
|
@@ -33,7 +33,10 @@ from fresco.util.http import encode_multipart
|
|
|
33
33
|
from fresco.util.urls import normpath, make_query
|
|
34
34
|
from fresco.util.common import fq_path
|
|
35
35
|
from fresco.util.wsgi import make_environ
|
|
36
|
-
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
|
|
37
40
|
|
|
38
41
|
from fresco.exceptions import ResponseException
|
|
39
42
|
from fresco.requestcontext import context
|
|
@@ -46,10 +49,12 @@ from fresco.routing import (
|
|
|
46
49
|
)
|
|
47
50
|
from fresco.options import Options
|
|
48
51
|
|
|
49
|
-
__all__ = ("FrescoApp", "urlfor")
|
|
52
|
+
__all__ = ("FrescoApp", "urlfor", "context")
|
|
50
53
|
|
|
51
54
|
logger = logging.getLogger(__name__)
|
|
52
55
|
|
|
56
|
+
ExcInfo = tuple[t.Type[BaseException], BaseException, types.TracebackType]
|
|
57
|
+
|
|
53
58
|
|
|
54
59
|
class FrescoApp(RouteCollection):
|
|
55
60
|
"""\
|
|
@@ -115,7 +120,9 @@ class FrescoApp(RouteCollection):
|
|
|
115
120
|
#: If a function returns a value (other than ``None``),
|
|
116
121
|
#: this value will be
|
|
117
122
|
#: returned as the response instead of calling the scheduled view.
|
|
118
|
-
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
|
+
] = []
|
|
119
126
|
|
|
120
127
|
#: Functions to be called if an exception is raised during a view
|
|
121
128
|
#: Each function will be passed ``request, exc_info``.
|
|
@@ -123,7 +130,9 @@ class FrescoApp(RouteCollection):
|
|
|
123
130
|
#: this value will be
|
|
124
131
|
#: returned as the response and the error will not be propagated.
|
|
125
132
|
#: If all exception handlers return None then the error will be raised
|
|
126
|
-
self.process_exception_handlers:
|
|
133
|
+
self.process_exception_handlers: list[
|
|
134
|
+
tuple[Type[Exception], Callable[[Request, ExcInfo], Union[Response, None]]]
|
|
135
|
+
] = []
|
|
127
136
|
|
|
128
137
|
#: Functions to be called at the end of request processing,
|
|
129
138
|
#: after all content has been output.
|
|
@@ -156,7 +165,7 @@ class FrescoApp(RouteCollection):
|
|
|
156
165
|
method: str,
|
|
157
166
|
currentcontext=context.currentcontext,
|
|
158
167
|
normpath=normpath,
|
|
159
|
-
):
|
|
168
|
+
) -> Response:
|
|
160
169
|
ctx = currentcontext()
|
|
161
170
|
ctx["app"] = self
|
|
162
171
|
environ = request.environ
|
|
@@ -178,19 +187,19 @@ class FrescoApp(RouteCollection):
|
|
|
178
187
|
|
|
179
188
|
try:
|
|
180
189
|
for traversal in self.get_route_traversals(path, method, request):
|
|
190
|
+
route = traversal.route
|
|
181
191
|
try:
|
|
182
|
-
route = traversal.route
|
|
183
192
|
environ["wsgiorg.routing_args"] = (
|
|
184
193
|
traversal.args,
|
|
185
194
|
traversal.kwargs,
|
|
186
195
|
)
|
|
187
|
-
view =
|
|
196
|
+
view = traversal.view
|
|
188
197
|
ctx["view_self"] = getattr(view, "__self__", None)
|
|
189
198
|
ctx["route_traversal"] = traversal
|
|
190
199
|
if self.logger:
|
|
191
200
|
self.logger.info(
|
|
192
201
|
"matched route: %s %r => %r",
|
|
193
|
-
|
|
202
|
+
method,
|
|
194
203
|
path,
|
|
195
204
|
fq_path(view),
|
|
196
205
|
)
|
|
@@ -271,7 +280,9 @@ class FrescoApp(RouteCollection):
|
|
|
271
280
|
except ResponseException as e:
|
|
272
281
|
response = e.response
|
|
273
282
|
else:
|
|
274
|
-
response = self.get_response(
|
|
283
|
+
response = self.get_response(
|
|
284
|
+
request, path, request.environ["REQUEST_METHOD"]
|
|
285
|
+
)
|
|
275
286
|
|
|
276
287
|
for f in self.process_response_handlers:
|
|
277
288
|
try:
|
|
@@ -324,12 +335,7 @@ class FrescoApp(RouteCollection):
|
|
|
324
335
|
exc_info=exc_info,
|
|
325
336
|
)
|
|
326
337
|
|
|
327
|
-
def handle_exception(
|
|
328
|
-
self, request, allow_reraise=True
|
|
329
|
-
) -> Union[
|
|
330
|
-
Response,
|
|
331
|
-
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
|
332
|
-
]:
|
|
338
|
+
def handle_exception(self, request, allow_reraise=True) -> Response:
|
|
333
339
|
exc_info = sys.exc_info()
|
|
334
340
|
if exc_info[0] is None:
|
|
335
341
|
raise AssertionError(
|
|
@@ -346,10 +352,7 @@ class FrescoApp(RouteCollection):
|
|
|
346
352
|
# server handle it
|
|
347
353
|
if allow_reraise and not have_error_handlers:
|
|
348
354
|
raise exc_info[1].with_traceback(exc_info[2]) # type: ignore
|
|
349
|
-
response:
|
|
350
|
-
Response,
|
|
351
|
-
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
|
352
|
-
] = Response.internal_server_error()
|
|
355
|
+
response: Response = Response.internal_server_error()
|
|
353
356
|
|
|
354
357
|
if not self.process_exception_handlers:
|
|
355
358
|
self.log_exception(request, exc_info)
|
|
@@ -406,7 +409,7 @@ class FrescoApp(RouteCollection):
|
|
|
406
409
|
self.reset_wsgi_app()
|
|
407
410
|
self._middleware.insert(position, (middleware, args, kwargs))
|
|
408
411
|
|
|
409
|
-
def make_wsgi_app(self, wsgi_app=None, use_middleware=True) ->
|
|
412
|
+
def make_wsgi_app(self, wsgi_app=None, use_middleware=True) -> WSGIApplication:
|
|
410
413
|
"""
|
|
411
414
|
Return a WSGI (PEP-3333) compliant application that drives this
|
|
412
415
|
FrescoApp object.
|
|
@@ -419,7 +422,7 @@ class FrescoApp(RouteCollection):
|
|
|
419
422
|
"""
|
|
420
423
|
if wsgi_app is None:
|
|
421
424
|
|
|
422
|
-
def
|
|
425
|
+
def _wsgi_app(
|
|
423
426
|
environ,
|
|
424
427
|
start_response,
|
|
425
428
|
view=self.view,
|
|
@@ -428,15 +431,18 @@ class FrescoApp(RouteCollection):
|
|
|
428
431
|
request = request_class(environ)
|
|
429
432
|
return view(request)(environ, start_response)
|
|
430
433
|
|
|
434
|
+
else:
|
|
435
|
+
_wsgi_app = wsgi_app
|
|
436
|
+
|
|
431
437
|
if use_middleware:
|
|
432
438
|
for m, m_args, m_kwargs in self._middleware:
|
|
433
|
-
|
|
439
|
+
_wsgi_app = m(_wsgi_app, *m_args, **m_kwargs)
|
|
434
440
|
|
|
435
441
|
def fresco_wsgi_app(
|
|
436
442
|
environ,
|
|
437
443
|
start_response,
|
|
438
444
|
frescoapp=self,
|
|
439
|
-
wsgi_app=
|
|
445
|
+
wsgi_app=_wsgi_app,
|
|
440
446
|
request_class=self.request_class,
|
|
441
447
|
process_teardown_handlers=self.process_teardown_handlers,
|
|
442
448
|
call_process_teardown_handlers=self.call_process_teardown_handlers,
|
|
@@ -602,7 +608,11 @@ class FrescoApp(RouteCollection):
|
|
|
602
608
|
self.process_response_handlers.append(func)
|
|
603
609
|
return func
|
|
604
610
|
|
|
605
|
-
def process_exception(
|
|
611
|
+
def process_exception(
|
|
612
|
+
self,
|
|
613
|
+
func: Callable[[Request, ExcInfo], Union[Response, None]],
|
|
614
|
+
exc_type: Type[Exception] = Exception,
|
|
615
|
+
):
|
|
606
616
|
"""
|
|
607
617
|
Register a ``process_exception`` hook function
|
|
608
618
|
"""
|
|
@@ -668,7 +678,9 @@ class FrescoApp(RouteCollection):
|
|
|
668
678
|
start_response("200 OK", [])
|
|
669
679
|
yield b""
|
|
670
680
|
|
|
671
|
-
def fake_start_response(
|
|
681
|
+
def fake_start_response(
|
|
682
|
+
status: str, headers: HeaderList, exc_info: OptionalExcInfo = None
|
|
683
|
+
) -> WriteCallable:
|
|
672
684
|
return lambda s: None
|
|
673
685
|
|
|
674
686
|
environ = make_environ(url, environ, wsgi_input, **kwargs)
|
|
@@ -693,12 +705,12 @@ class FrescoApp(RouteCollection):
|
|
|
693
705
|
if multipart:
|
|
694
706
|
wsgi_input, headers = encode_multipart(data, files)
|
|
695
707
|
kwargs.update(headers)
|
|
696
|
-
elif
|
|
708
|
+
elif isinstance(data, t.BinaryIO):
|
|
697
709
|
wsgi_input = data.read()
|
|
698
710
|
elif isinstance(data, bytes):
|
|
699
711
|
wsgi_input = data
|
|
700
712
|
elif data is None:
|
|
701
|
-
wsgi_input = ""
|
|
713
|
+
wsgi_input = b""
|
|
702
714
|
else:
|
|
703
715
|
wsgi_input = make_query(data).encode("ascii")
|
|
704
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT_CHARSET = "UTF-8"
|
|
@@ -12,11 +12,17 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
|
+
import typing as t
|
|
16
|
+
|
|
17
|
+
from fresco.types import WSGIApplication
|
|
18
|
+
from fresco.types import WSGIEnviron
|
|
19
|
+
from fresco.types import StartResponse
|
|
20
|
+
|
|
15
21
|
__all__ = ["XForwarded"]
|
|
16
22
|
|
|
17
23
|
|
|
18
24
|
class XForwarded(object):
|
|
19
|
-
"""
|
|
25
|
+
"""
|
|
20
26
|
Modify the WSGI environment so that the X_FORWARDED_* headers are observed
|
|
21
27
|
and generated URIs are correct in a proxied environment.
|
|
22
28
|
|
|
@@ -33,6 +39,13 @@ class XForwarded(object):
|
|
|
33
39
|
the wsgi.url_scheme is modified to ``https`` and ``HTTPS`` is set to
|
|
34
40
|
``on``.
|
|
35
41
|
|
|
42
|
+
:param trusted:
|
|
43
|
+
List of IP addresses trusted to set the HTTP_X_FORWARDED_* headers
|
|
44
|
+
|
|
45
|
+
:param force_https:
|
|
46
|
+
If True, the following environ keys will be set unconditionally:
|
|
47
|
+
``"HTTPS": "on"`` and ``"wsgi.url_scheme": "https"`` will be set
|
|
48
|
+
|
|
36
49
|
Example::
|
|
37
50
|
|
|
38
51
|
>>> from fresco import FrescoApp, context, GET, Response
|
|
@@ -68,24 +81,36 @@ class XForwarded(object):
|
|
|
68
81
|
u'URL is https://real-name/; REMOTE_ADDR is 1.2.3.4'
|
|
69
82
|
"""
|
|
70
83
|
|
|
71
|
-
def __init__(
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
app: WSGIApplication,
|
|
87
|
+
trusted: t.Optional[t.Iterable[str]] = None,
|
|
88
|
+
force_https: t.Optional[bool] = None,
|
|
89
|
+
) -> None:
|
|
72
90
|
self.app = app
|
|
91
|
+
self.force_https = force_https
|
|
73
92
|
if trusted:
|
|
74
93
|
self.trusted = set(trusted)
|
|
75
94
|
else:
|
|
76
95
|
self.trusted = set()
|
|
77
96
|
|
|
78
|
-
def __call__(
|
|
79
|
-
|
|
97
|
+
def __call__(
|
|
98
|
+
self, environ: WSGIEnviron, start_response: StartResponse
|
|
99
|
+
) -> t.Iterable[bytes]:
|
|
100
|
+
"""
|
|
80
101
|
Call the WSGI app, passing it a modified environ
|
|
81
102
|
"""
|
|
82
103
|
env = environ.get
|
|
83
|
-
is_ssl = (
|
|
84
|
-
env("HTTP_X_FORWARDED_PROTO") == "https"
|
|
85
|
-
or env("HTTP_X_FORWARDED_SSL") == "on"
|
|
86
|
-
)
|
|
87
104
|
|
|
105
|
+
if self.force_https is None:
|
|
106
|
+
is_ssl = (
|
|
107
|
+
env("HTTP_X_FORWARDED_PROTO") == "https"
|
|
108
|
+
or env("HTTP_X_FORWARDED_SSL") == "on"
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
is_ssl = self.force_https
|
|
88
112
|
host = env("HTTP_X_FORWARDED_HOST")
|
|
113
|
+
|
|
89
114
|
if host is not None:
|
|
90
115
|
if ":" in host:
|
|
91
116
|
port = host.split(":")[1]
|
|
@@ -99,21 +124,17 @@ class XForwarded(object):
|
|
|
99
124
|
environ["wsgi.url_scheme"] = "https"
|
|
100
125
|
environ["HTTPS"] = "on"
|
|
101
126
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
environ["REMOTE_ADDR"] = ip
|
|
115
|
-
break
|
|
116
|
-
else:
|
|
117
|
-
environ["REMOTE_ADDR"] = forwards[0]
|
|
127
|
+
forwarded_for = env("HTTP_X_FORWARDED_FOR")
|
|
128
|
+
if forwarded_for:
|
|
129
|
+
addrs = forwarded_for.split(", ")
|
|
130
|
+
|
|
131
|
+
if self.trusted:
|
|
132
|
+
for ip in addrs[::-1]:
|
|
133
|
+
# Find the first non-trusted ip; this is our remote address
|
|
134
|
+
if ip not in self.trusted:
|
|
135
|
+
environ["REMOTE_ADDR"] = ip
|
|
136
|
+
break
|
|
137
|
+
else:
|
|
138
|
+
environ["REMOTE_ADDR"] = addrs[0]
|
|
118
139
|
|
|
119
140
|
return self.app(environ, start_response)
|