diff --git a/falcon-4.0.2.tar.gz b/falcon-4.0.2.tar.gz deleted file mode 100644 index 327bacd..0000000 --- a/falcon-4.0.2.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58f4b9c9da4c9b1e2c9f396ad7ef897701b3c7c7c87227f0bd1aee40c7fbc525 -size 630121 diff --git a/falcon-4.1.0.tar.gz b/falcon-4.1.0.tar.gz new file mode 100644 index 0000000..643b0ea --- /dev/null +++ b/falcon-4.1.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbc3fa642b43e7662f121d0a7b5d7ea42a1a31fb22ae63572c42ee3ecd1f79d0 +size 659308 diff --git a/python-falcon.changes b/python-falcon.changes index f33f789..915312a 100644 --- a/python-falcon.changes +++ b/python-falcon.changes @@ -1,3 +1,60 @@ +------------------------------------------------------------------- +Tue Oct 28 09:03:20 UTC 2025 - John Paul Adrian Glaubitz + +- Update to 4.1.0 + * CPython 3.14 is now fully supported. (#2413) + * Although the Falcon 4.x series is only guaranteed to support Python 3.10+, + this release still supports 3.8 & 3.9 at runtime using the pure Python wheel. + * Falcon 4.2 is expected to drop the end-of-life Python 3.8 completely (but + runtime support will continue for 3.9 on a best effort basis). + * StaticRoute now renders Etag headers. It also checks If-None-Match in requests + and returns HTTP 304 response if appropriate. (#2243) + * StaticRoute now sets the Last-Modified header when serving static files. The + improved implementation also checks the value of the If-Modified-Since header, + and renders an HTTP 304 response when the requested file has not been modified. (#2244) + * Similar to create_environ(), the create_scope() testing helper now preserves the + raw URI path, and propagates it to the created ASGI connection scope as the + raw_path byte string (according to the ASGI specification). (#2262) + * Two new media_type constants, falcon.MEDIA_CSV and falcon.MEDIA_PARQUET, were + added in order to provide better support for Python data analysis applications + out of the box. (#2335) + * Support for allowing cross-origin private network access was added to the built-in + CORSMiddleware. The new feature is off by default, and can be enabled by passing + the keyword argument allow_private_network=True to CORSMiddleware during + initialization. (#2381) + * The falcon.secure_filename() utility function can now ensure that the length of the + sanitized filename does not exceed the requested limit (passed via the max_length + argument). In addition, a new option, max_secure_filename_length, was added to + MultipartParseOptions in order to automatically populate this argument when + referencing a body part’s secure_filename. (#2420) + * The unset_cookie() method now accepts a same_site parameter (with underscore) for + consistency with set_cookie(). The previous samesite parameter (without underscore) + is now deprecated (referencing it will emit a deprecation warning). (#2453) + * A new method, __rich__, has been added to falcon.testing.Result for facilitating + a rich-text representation when used together with the popular rich library. + * The cythonization process was revised in the light of the performance improvements + in newer CPython versions (especially 3.12+), and the compilation is now largely + confined to hand-crafted C/Cython code. As a result, the framework should run even + faster on modern CPython. (#2470) + * JSONHandler can now detect a non-standard (not a subclass of ValueError) deserialization + error type for a custom loads function. + (Normally, json.loads() and third party alternatives do raise a subclass of ValueError + on invalid input data, however, this is not the case for, e.g., the popular msgspec + library at the time of writing.) (#2476) + * Previously, Falcon’s WebSocket implementation was not documented to route the request + to any sink. However, in the case of a missing route, a matching sink was actually + invoked, passing ws in place of the incompatible resp. + This mismatch has been addressed by introducing a ws keyword argument (similar to + ASGI error handlers) for sink functions meant to accept WebSocket connections. + For backwards-compatibility, when ws is absent from the sink’s signature, the WebSocket + object is still passed in place of the incompatible resp. This behavior will change in + Falcon 5.0: when draining a WebSocket connection, resp will always be set to None. (#2414) + * The readability of the Contributing docs was improved by properly rendering GitHub + Markdown-flavored checkboxes. (#2318) + * The falcon.testing.httpnow compatibility alias is now considered deprecated, and will + be removed in Falcon 5.0. Use the falcon.http_now() function instead. (#2389) +- Drop websockets.patch, merged upstream + ------------------------------------------------------------------- Tue Aug 19 12:18:37 UTC 2025 - Markéta Machová diff --git a/python-falcon.spec b/python-falcon.spec index 8957048..a5dc35e 100644 --- a/python-falcon.spec +++ b/python-falcon.spec @@ -28,14 +28,12 @@ %endif %{?sle15_python_module_pythons} Name: python-falcon -Version: 4.0.2 +Version: 4.1.0 Release: 0 Summary: A web framework for building APIs and app backends License: Apache-2.0 URL: https://falconframework.org Source: https://files.pythonhosted.org/packages/source/f/falcon/falcon-%{version}.tar.gz -# PATCH-FIX-UPSTREAM https://github.com/falconry/falcon/pull/2406 chore(tests/asgi): migrate to the new websockets async client -Patch0: websockets.patch BuildRequires: %{python_module PyYAML} BuildRequires: %{python_module Sphinx} BuildRequires: %{python_module base >= 3.8} diff --git a/websockets.patch b/websockets.patch deleted file mode 100644 index c71261b..0000000 --- a/websockets.patch +++ /dev/null @@ -1,216 +0,0 @@ -From cf51816a2f7cd7a23d3e1129fe9418a5fc85d8be Mon Sep 17 00:00:00 2001 -From: Vytautas Liuolia -Date: Mon, 11 Nov 2024 08:43:02 +0100 -Subject: [PATCH] chore(tests/asgi): migrate to the new `websockets` async - client (#2406) - -* chore(tests/asgi): migrate to the new `websockets` async client - -* chore: update the unsupported WS protocol exception for Daphne/Hypercorn ---- - requirements/tests | 2 +- - tests/asgi/test_asgi_servers.py | 67 +++++++++++++++++---------------- - 2 files changed, 35 insertions(+), 34 deletions(-) - -diff --git a/requirements/tests b/requirements/tests -index ada7c3729..36825fd23 100644 ---- a/requirements/tests -+++ b/requirements/tests -@@ -13,7 +13,7 @@ testtools; python_version < '3.10' - aiofiles - httpx - uvicorn >= 0.17.0 --websockets -+websockets >= 13.1 - - # Handler Specific - cbor2 -diff --git a/tests/asgi/test_asgi_servers.py b/tests/asgi/test_asgi_servers.py -index eb35ac62d..044d46a38 100644 ---- a/tests/asgi/test_asgi_servers.py -+++ b/tests/asgi/test_asgi_servers.py -@@ -24,6 +24,7 @@ - - try: - import websockets -+ import websockets.asyncio.client - import websockets.exceptions - except ImportError: - websockets = None # type: ignore -@@ -232,9 +233,9 @@ async def test_hello( - if close_code: - extra_headers['X-Close-Code'] = str(close_code) - -- async with websockets.connect( -+ async with websockets.asyncio.client.connect( - server_url_events_ws, -- extra_headers=extra_headers, -+ additional_headers=extra_headers, - ) as ws: - got_message = False - -@@ -273,22 +274,22 @@ async def test_rejected(self, explicit_close, close_code, server_url_events_ws): - if close_code: - extra_headers['X-Close-Code'] = str(close_code) - -- with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info: -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ with pytest.raises(websockets.exceptions.InvalidStatus) as exc_info: -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ): - pass - -- assert exc_info.value.status_code == 403 -+ assert exc_info.value.response.status_code == 403 - - async def test_missing_responder(self, server_url_events_ws): - server_url_events_ws += '/404' - -- with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info: -- async with websockets.connect(server_url_events_ws): -+ with pytest.raises(websockets.exceptions.InvalidStatus) as exc_info: -+ async with websockets.asyncio.client.connect(server_url_events_ws): - pass - -- assert exc_info.value.status_code == 403 -+ assert exc_info.value.response.status_code == 403 - - @pytest.mark.parametrize( - 'subprotocol, expected', -@@ -301,9 +302,9 @@ async def test_select_subprotocol_known( - self, subprotocol, expected, server_url_events_ws - ): - extra_headers = {'X-Subprotocol': subprotocol} -- async with websockets.connect( -+ async with websockets.asyncio.client.connect( - server_url_events_ws, -- extra_headers=extra_headers, -+ additional_headers=extra_headers, - subprotocols=['amqp', 'wamp'], - ) as ws: - assert ws.subprotocol == expected -@@ -312,9 +313,9 @@ async def test_select_subprotocol_unknown(self, server_url_events_ws): - extra_headers = {'X-Subprotocol': 'xmpp'} - - try: -- async with websockets.connect( -+ async with websockets.asyncio.client.connect( - server_url_events_ws, -- extra_headers=extra_headers, -+ additional_headers=extra_headers, - subprotocols=['amqp', 'wamp'], - ): - pass -@@ -329,8 +330,8 @@ async def test_select_subprotocol_unknown(self, server_url_events_ws): - except websockets.exceptions.NegotiationError as ex: - assert 'unsupported subprotocol: xmpp' in str(ex) - -- # Daphne -- except websockets.exceptions.InvalidMessage: -+ # Daphne, Hypercorn -+ except EOFError: - pass - - # NOTE(kgriffs): When executing this test under pytest with the -s -@@ -340,8 +341,8 @@ async def test_select_subprotocol_unknown(self, server_url_events_ws): - # but the usual ways of capturing stdout/stderr with pytest do - # not work. - async def test_disconnecting_client_early(self, server_url_events_ws): -- ws = await websockets.connect( -- server_url_events_ws, extra_headers={'X-Close': 'True'} -+ ws = await websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers={'X-Close': 'True'} - ) - await asyncio.sleep(0.2) - -@@ -361,8 +362,8 @@ async def test_disconnecting_client_early(self, server_url_events_ws): - async def test_send_before_accept(self, server_url_events_ws): - extra_headers = {'x-accept': 'skip'} - -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ) as ws: - message = await ws.recv() - assert message == 'OperationNotAllowed' -@@ -370,8 +371,8 @@ async def test_send_before_accept(self, server_url_events_ws): - async def test_recv_before_accept(self, server_url_events_ws): - extra_headers = {'x-accept': 'skip', 'x-command': 'recv'} - -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ) as ws: - message = await ws.recv() - assert message == 'OperationNotAllowed' -@@ -379,8 +380,8 @@ async def test_recv_before_accept(self, server_url_events_ws): - async def test_invalid_close_code(self, server_url_events_ws): - extra_headers = {'x-close': 'True', 'x-close-code': 42} - -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ) as ws: - start = time.time() - -@@ -395,22 +396,22 @@ async def test_invalid_close_code(self, server_url_events_ws): - async def test_close_code_on_unhandled_error(self, server_url_events_ws): - extra_headers = {'x-raise-error': 'generic'} - -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ) as ws: - await ws.wait_closed() - -- assert ws.close_code in {3011, 1011} -+ assert ws.protocol.close_code in {3011, 1011} - - async def test_close_code_on_unhandled_http_error(self, server_url_events_ws): - extra_headers = {'x-raise-error': 'http'} - -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ) as ws: - await ws.wait_closed() - -- assert ws.close_code == 3400 -+ assert ws.protocol.close_code == 3400 - - @pytest.mark.parametrize('mismatch', ['send', 'recv']) - @pytest.mark.parametrize('mismatch_type', ['text', 'data']) -@@ -420,8 +421,8 @@ async def test_type_mismatch(self, mismatch, mismatch_type, server_url_events_ws - 'X-Mismatch-Type': mismatch_type, - } - -- async with websockets.connect( -- server_url_events_ws, extra_headers=extra_headers -+ async with websockets.asyncio.client.connect( -+ server_url_events_ws, additional_headers=extra_headers - ) as ws: - if mismatch == 'recv': - if mismatch_type == 'text': -@@ -431,13 +432,13 @@ async def test_type_mismatch(self, mismatch, mismatch_type, server_url_events_ws - - await ws.wait_closed() - -- assert ws.close_code in {3011, 1011} -+ assert ws.protocol.close_code in {3011, 1011} - - async def test_passing_path_params(self, server_base_url_ws): - expected_feed_id = '1ee7' - url = f'{server_base_url_ws}feeds/{expected_feed_id}' - -- async with websockets.connect(url) as ws: -+ async with websockets.asyncio.client.connect(url) as ws: - feed_id = await ws.recv() - assert feed_id == expected_feed_id -