From 9e9e3cdf5de1e1a67ce7a68633350e04fc4d1d3c Mon Sep 17 00:00:00 2001 From: arthurprioli Date: Fri, 18 Oct 2024 18:39:09 -0300 Subject: [PATCH 01/11] tried to add support for messagepack --- falcon/testing/client.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 1521128e7..d655eeec1 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -30,6 +30,7 @@ from falcon.asgi_spec import ScopeType from falcon.constants import COMBINED_METHODS from falcon.constants import MEDIA_JSON +from falcon.constants import MEDIA_MSGPACK from falcon.errors import CompatibilityError from falcon.testing import helpers from falcon.testing.srmock import StartResponseMock @@ -39,6 +40,7 @@ from falcon.util import http_cookies from falcon.util import http_date_to_dt from falcon.util import to_query_str +from falcon.media import MessagePackHandler warnings.filterwarnings( 'error', @@ -442,6 +444,7 @@ def simulate_request( content_type=None, body=None, json=None, + msgpack=None, file_wrapper=None, wsgierrors=None, params=None, @@ -579,6 +582,7 @@ def simulate_request( content_type=content_type, body=body, json=json, + msgpack=msgpack, params=params, params_csv=params_csv, protocol=protocol, @@ -602,6 +606,7 @@ def simulate_request( headers, body, json, + msgpack, extras, ) @@ -655,6 +660,7 @@ async def _simulate_request_asgi( content_type=None, body=None, json=None, + msgpack=None, params=None, params_csv=True, protocol='http', @@ -777,6 +783,7 @@ async def _simulate_request_asgi( headers, body, json, + msgpack, extras, ) @@ -2144,7 +2151,7 @@ async def __aexit__(self, exc_type, exc, tb): def _prepare_sim_args( - path, query_string, params, params_csv, content_type, headers, body, json, extras + path, query_string, params, params_csv, content_type, headers, body, json, msgpack, extras ): if not path.startswith('/'): raise ValueError("path must start with '/'") @@ -2178,6 +2185,11 @@ def _prepare_sim_args( headers = headers or {} headers['Content-Type'] = MEDIA_JSON + if msgpack is not None: + body = MessagePackHandler.serialize(msgpack) + headers = headers or {} + headers['Content-Type'] = MEDIA_MSGPACK + return path, query_string, headers, body, extras From 8deb751ebdee892f882e6572a61485a19be4ed6a Mon Sep 17 00:00:00 2001 From: arthurprioli Date: Sat, 19 Oct 2024 22:16:46 -0300 Subject: [PATCH 02/11] added documentation --- falcon/testing/client.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index d655eeec1..a00dd40a1 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -32,6 +32,7 @@ from falcon.constants import MEDIA_JSON from falcon.constants import MEDIA_MSGPACK from falcon.errors import CompatibilityError +from falcon.media import MessagePackHandler from falcon.testing import helpers from falcon.testing.srmock import StartResponseMock from falcon.util import async_to_sync @@ -40,7 +41,7 @@ from falcon.util import http_cookies from falcon.util import http_date_to_dt from falcon.util import to_query_str -from falcon.media import MessagePackHandler + warnings.filterwarnings( 'error', @@ -745,6 +746,11 @@ async def _simulate_request_asgi( overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1471,6 +1477,11 @@ def simulate_post(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1582,6 +1593,11 @@ def simulate_put(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1782,6 +1798,11 @@ def simulate_patch(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1888,6 +1909,11 @@ def simulate_delete(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the From 97a5a2afa2d26e6ab14cc9241be26079122a37ce Mon Sep 17 00:00:00 2001 From: arthurprioli Date: Sat, 19 Oct 2024 22:34:13 -0300 Subject: [PATCH 03/11] fixed ruff formatting --- falcon/testing/client.py | 62 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index a00dd40a1..014179512 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -42,7 +42,6 @@ from falcon.util import http_date_to_dt from falcon.util import to_query_str - warnings.filterwarnings( 'error', ('Unknown REQUEST_METHOD: ' + "'({})'".format('|'.join(COMBINED_METHODS))), @@ -746,11 +745,11 @@ async def _simulate_request_asgi( overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1477,11 +1476,11 @@ def simulate_post(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1593,11 +1592,11 @@ def simulate_put(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1798,11 +1797,11 @@ def simulate_patch(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1909,11 +1908,11 @@ def simulate_delete(app, path, **kwargs) -> _ResultBase: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -2177,7 +2176,16 @@ async def __aexit__(self, exc_type, exc, tb): def _prepare_sim_args( - path, query_string, params, params_csv, content_type, headers, body, json, msgpack, extras + path, + query_string, + params, + params_csv, + content_type, + headers, + body, + json, + msgpack, + extras, ): if not path.startswith('/'): raise ValueError("path must start with '/'") From 2cea4f675fc163181899c39195082dd3079a8589 Mon Sep 17 00:00:00 2001 From: arthurprioli Date: Wed, 30 Oct 2024 16:16:41 -0300 Subject: [PATCH 04/11] tests added --- docs/_newsfragments/2387.misc.rst | 7 ------- docs/changes/4.1.0.rst | 14 +++++++++++++- falcon/testing/client.py | 6 +++--- tests/test_testing.py | 14 ++++++++++++++ 4 files changed, 30 insertions(+), 11 deletions(-) delete mode 100644 docs/_newsfragments/2387.misc.rst diff --git a/docs/_newsfragments/2387.misc.rst b/docs/_newsfragments/2387.misc.rst deleted file mode 100644 index 0aa219996..000000000 --- a/docs/_newsfragments/2387.misc.rst +++ /dev/null @@ -1,7 +0,0 @@ -Running mypy on code that uses parts of ``falcon.testing`` naively -would lead to errors like:: - - Name "falcon.testing.TestClient" is not defined - -This has been fixed by explicitly exporting the names that are -imported in the ``falcon.testing`` namespace. diff --git a/docs/changes/4.1.0.rst b/docs/changes/4.1.0.rst index c9a6f005a..99997c226 100644 --- a/docs/changes/4.1.0.rst +++ b/docs/changes/4.1.0.rst @@ -14,9 +14,21 @@ Changes to Supported Platforms .. NOTE(vytas): No changes to the supported platforms (yet). - .. towncrier release notes start + +Misc +---- + +- Running mypy on code that uses parts of ``falcon.testing`` naively + would lead to errors like:: + + Name "falcon.testing.TestClient" is not defined + + This has been fixed by explicitly exporting the names that are + imported in the ``falcon.testing`` namespace. (`#2387 `__) + + Contributors to this Release ---------------------------- diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 25caeb4ea..89a0eff1a 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -742,7 +742,6 @@ async def _simulate_request_asgi( asgi_chunk_size: int = 4096, asgi_disconnect_ttl: int = 300, cookies: Optional[CookieArg] = None, - # NOTE(kgriffs): These are undocumented because they are only # meant to be used internally by the framework (i.e., they are # not part of the public interface.) In case we ever expose @@ -2273,7 +2272,6 @@ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: def _prepare_sim_args( - path: str, query_string: Optional[str], params: Optional[Mapping[str, Any]], @@ -2320,7 +2318,9 @@ def _prepare_sim_args( headers['Content-Type'] = MEDIA_JSON if msgpack is not None: - body = MessagePackHandler.serialize(msgpack) + body = MessagePackHandler.serialize( + MessagePackHandler(), content_type=None, media=msgpack + ) headers = headers or {} headers['Content-Type'] = MEDIA_MSGPACK diff --git a/tests/test_testing.py b/tests/test_testing.py index 89a8c49e8..5d8760da0 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -103,6 +103,20 @@ def on_post(self, req, resp): ) assert result.text == falcon.MEDIA_JSON + result = testing.simulate_post(app, '/', msgpack={}) + assert result.text == falcon.MEDIA_MSGPACK + + result = testing.simulate_post(app, '/', msgpack={}, content_type=falcon.MEDIA_HTML) + assert result.text == falcon.MEDIA_MSGPACK + + result = testing.simulate_post(app, '/', msgpack={}, headers=headers) + assert result.text == falcon.MEDIA_MSGPACK + + result = testing.simulate_post( + app, '/', msgpack={}, headers=headers, content_type=falcon.MEDIA_HTML + ) + assert result.text == falcon.MEDIA_MSGPACK + @pytest.mark.parametrize('mode', ['wsgi', 'asgi', 'asgi-stream']) def test_content_type(util, mode): From f911793abfcee8cdd2b7957c5bfad62e87173610 Mon Sep 17 00:00:00 2001 From: arthurprioli Date: Wed, 30 Oct 2024 17:26:29 -0300 Subject: [PATCH 05/11] inserted changes asked by code review --- falcon/testing/client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 89a0eff1a..da4816f7a 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -814,11 +814,11 @@ async def _simulate_request_asgi( overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the From aa6edf263d2b713a715281b41483f7efc415f84b Mon Sep 17 00:00:00 2001 From: arthurprioli Date: Wed, 30 Oct 2024 17:29:04 -0300 Subject: [PATCH 06/11] fixed overidented docstrings --- falcon/testing/client.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index da4816f7a..8be0a9af8 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -1563,11 +1563,11 @@ def simulate_post(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1679,11 +1679,11 @@ def simulate_put(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1884,11 +1884,11 @@ def simulate_patch(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + `'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1995,11 +1995,11 @@ def simulate_delete(app: Callable[..., Any], path: str, **kwargs: Any) -> Result overrides `body` and sets the Content-Type header to ``'application/json'``, overriding any value specified by either the `content_type` or `headers` arguments. - msgpack(Msgpack serializable): A Msgpack document to serialize as the - body of the request (default: ``None``). If specified, - overrides `body` and sets the Content-Type header to - ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + msgpack(Msgpack serializable): A Msgpack document to serialize as the + body of the request (default: ``None``). If specified, + overrides `body` and sets the Content-Type header to + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the From 918ba30d332e31933e927134d14489abee6249b9 Mon Sep 17 00:00:00 2001 From: Arthur Prioli Date: Wed, 20 Nov 2024 16:34:26 -0300 Subject: [PATCH 07/11] added more tests, fixed documentation and wrote better client.py --- falcon/testing/client.py | 21 ++++++++++++--------- tests/test_testing.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 8be0a9af8..cf1cdc47a 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -818,7 +818,8 @@ async def _simulate_request_asgi( body of the request (default: ``None``). If specified, overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + either the `content_type` or `headers` arguments. If msgpack and json + are both specified, the Content-Type header will be set as ``'application/msgpack'``. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1567,7 +1568,8 @@ def simulate_post(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: body of the request (default: ``None``). If specified, overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + either the `content_type` or `headers` arguments. If msgpack and json + are both specified, the Content-Type header will be set as ``'application/msgpack'``. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1683,7 +1685,8 @@ def simulate_put(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: body of the request (default: ``None``). If specified, overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + either the `content_type` or `headers` arguments. If msgpack and json + are both specified, the Content-Type header will be set as ``'application/msgpack'``. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1887,8 +1890,9 @@ def simulate_patch(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: msgpack(Msgpack serializable): A Msgpack document to serialize as the body of the request (default: ``None``). If specified, overrides `body` and sets the Content-Type header to - `'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + ``'application/msgpack'``, overriding any value specified by + either the `content_type` or `headers` arguments. If msgpack and json + are both specified, the Content-Type header will be set as ``'application/msgpack'``. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1999,7 +2003,8 @@ def simulate_delete(app: Callable[..., Any], path: str, **kwargs: Any) -> Result body of the request (default: ``None``). If specified, overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by - either the `content_type` or `headers` arguments. + either the `content_type` or `headers` arguments. If msgpack and json + are both specified, the Content-Type header will be set as ``'application/msgpack'``. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -2318,9 +2323,7 @@ def _prepare_sim_args( headers['Content-Type'] = MEDIA_JSON if msgpack is not None: - body = MessagePackHandler.serialize( - MessagePackHandler(), content_type=None, media=msgpack - ) + body = MessagePackHandler().serialize(content_type=None, media=msgpack) headers = headers or {} headers['Content-Type'] = MEDIA_MSGPACK diff --git a/tests/test_testing.py b/tests/test_testing.py index 5d8760da0..e24410069 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -103,6 +103,22 @@ def on_post(self, req, resp): ) assert result.text == falcon.MEDIA_JSON + result = testing.simulate_post(app, '/', json={}, msgpack={}) + assert result.text == falcon.MEDIA_MSGPACK + + result = testing.simulate_post(app, '/', json={}, msgpack={}, headers=headers) + assert result.text == falcon.MEDIA_MSGPACK + + result = testing.simulate_post( + app, '/', json={}, msgpack={}, content_type=falcon.MEDIA_HTML + ) + assert result.text == falcon.MEDIA_MSGPACK + + result = testing.simulate_post( + app, '/', json={}, msgpack={}, headers=headers, content_type=falcon.MEDIA_HTML + ) + assert result.text == falcon.MEDIA_MSGPACK + result = testing.simulate_post(app, '/', msgpack={}) assert result.text == falcon.MEDIA_MSGPACK From 41965f6e0fcb51b3ccfe46cd115982abc26a64fa Mon Sep 17 00:00:00 2001 From: Arthur Menezes Date: Fri, 3 Jan 2025 17:38:57 -0300 Subject: [PATCH 08/11] removed diffs from newsfragment --- docs/changes/4.1.0.rst | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/docs/changes/4.1.0.rst b/docs/changes/4.1.0.rst index 36f533cd5..5a9d8dc43 100644 --- a/docs/changes/4.1.0.rst +++ b/docs/changes/4.1.0.rst @@ -14,20 +14,8 @@ on GitHub. .. NOTE(vytas): No changes to the supported platforms (yet). -.. towncrier release notes start - - -Misc ----- - -- Running mypy on code that uses parts of ``falcon.testing`` naively - would lead to errors like:: - - Name "falcon.testing.TestClient" is not defined - - This has been fixed by explicitly exporting the names that are - imported in the ``falcon.testing`` namespace. (`#2387 `__) +.. towncrier release notes start Contributors to this Release ---------------------------- From 9a4adeda39ece60f57b342b125c737b345a5b694 Mon Sep 17 00:00:00 2001 From: Arthur Menezes Date: Tue, 7 Jan 2025 16:04:24 -0300 Subject: [PATCH 09/11] added new test function for msgpack parameters and fixed small bugs --- falcon/testing/client.py | 19 ++++++++----- tests/test_testing.py | 58 +++++++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index cf1cdc47a..cd84d58a7 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -672,6 +672,7 @@ async def _simulate_request_asgi( content_type: Optional[str] = ..., body: Optional[Union[str, bytes]] = ..., json: Optional[Any] = ..., + msgpack: Optional[Any] = ..., params: Optional[Mapping[str, Any]] = ..., params_csv: bool = ..., protocol: str = ..., @@ -699,6 +700,7 @@ async def _simulate_request_asgi( content_type: Optional[str] = ..., body: Optional[Union[str, bytes]] = ..., json: Optional[Any] = ..., + msgpack: Optional[Any] = ..., params: Optional[Mapping[str, Any]] = ..., params_csv: bool = ..., protocol: str = ..., @@ -819,7 +821,8 @@ async def _simulate_request_asgi( overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by either the `content_type` or `headers` arguments. If msgpack and json - are both specified, the Content-Type header will be set as ``'application/msgpack'``. + are both specified, the Content-Type header will be set as ` + `'application/msgpack'``. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -1569,7 +1572,8 @@ def simulate_post(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by either the `content_type` or `headers` arguments. If msgpack and json - are both specified, the Content-Type header will be set as ``'application/msgpack'``. + are both specified, the Content-Type header will be set as + ``'application/msgpack'``. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1686,7 +1690,8 @@ def simulate_put(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by either the `content_type` or `headers` arguments. If msgpack and json - are both specified, the Content-Type header will be set as ``'application/msgpack'``. + are both specified, the Content-Type header will be set as + ``'application/msgpack'``. file_wrapper (callable): Callable that returns an iterable, to be used as the value for *wsgi.file_wrapper* in the WSGI environ (default: ``None``). This can be used to test @@ -1892,7 +1897,8 @@ def simulate_patch(app: Callable[..., Any], path: str, **kwargs: Any) -> Result: overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by either the `content_type` or `headers` arguments. If msgpack and json - are both specified, the Content-Type header will be set as ``'application/msgpack'``. + are both specified, the Content-Type header will be set as + ``'application/msgpack'``. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -2004,7 +2010,8 @@ def simulate_delete(app: Callable[..., Any], path: str, **kwargs: Any) -> Result overrides `body` and sets the Content-Type header to ``'application/msgpack'``, overriding any value specified by either the `content_type` or `headers` arguments. If msgpack and json - are both specified, the Content-Type header will be set as ``'application/msgpack'``. + are both specified, the Content-Type header will be set as + ``'application/msgpack'``. host(str): A string to use for the hostname part of the fully qualified request URL (default: 'falconframework.org') remote_addr (str): A string to use as the remote IP address for the @@ -2324,7 +2331,7 @@ def _prepare_sim_args( if msgpack is not None: body = MessagePackHandler().serialize(content_type=None, media=msgpack) - headers = headers or {} + headers = dict(headers or {}) headers['Content-Type'] = MEDIA_MSGPACK return path, query_string, headers, body, extras diff --git a/tests/test_testing.py b/tests/test_testing.py index e24410069..a1ca63ab2 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -6,6 +6,13 @@ from falcon import testing from falcon.util.sync import async_to_sync +try: + import msgpack +except ImportError: + msgpack = None + +SAMPLE_BODY = testing.rand_string(0, 128 * 1024) + class CustomCookies: def items(self): @@ -103,35 +110,48 @@ def on_post(self, req, resp): ) assert result.text == falcon.MEDIA_JSON - result = testing.simulate_post(app, '/', json={}, msgpack={}) - assert result.text == falcon.MEDIA_MSGPACK - result = testing.simulate_post(app, '/', json={}, msgpack={}, headers=headers) - assert result.text == falcon.MEDIA_MSGPACK +@pytest.mark.skipif(not msgpack, reason='msgpack not installed') +@pytest.mark.parametrize( + 'json,msgpack,response', + [ + ({}, None, falcon.MEDIA_JSON), + (None, {}, falcon.MEDIA_MSGPACK), + ({}, {}, falcon.MEDIA_MSGPACK), + ], +) +def test_simulate_request_msgpack_content_type(json, msgpack, response): + class Foo: + def on_post(self, req, resp): + resp.text = req.content_type + + app = App() + app.add_route('/', Foo()) + + headers = {'Content-Type': falcon.MEDIA_TEXT} + + result = testing.simulate_post(app, '/', json=json, msgpack=msgpack) + assert result.text == response result = testing.simulate_post( - app, '/', json={}, msgpack={}, content_type=falcon.MEDIA_HTML + app, '/', json=json, msgpack=msgpack, content_type=falcon.MEDIA_HTML ) - assert result.text == falcon.MEDIA_MSGPACK + assert result.text == response result = testing.simulate_post( - app, '/', json={}, msgpack={}, headers=headers, content_type=falcon.MEDIA_HTML + app, '/', json=json, msgpack=msgpack, headers=headers ) - assert result.text == falcon.MEDIA_MSGPACK - - result = testing.simulate_post(app, '/', msgpack={}) - assert result.text == falcon.MEDIA_MSGPACK - - result = testing.simulate_post(app, '/', msgpack={}, content_type=falcon.MEDIA_HTML) - assert result.text == falcon.MEDIA_MSGPACK - - result = testing.simulate_post(app, '/', msgpack={}, headers=headers) - assert result.text == falcon.MEDIA_MSGPACK + assert result.text == response result = testing.simulate_post( - app, '/', msgpack={}, headers=headers, content_type=falcon.MEDIA_HTML + app, + '/', + json=json, + msgpack=msgpack, + headers=headers, + content_type=falcon.MEDIA_HTML, ) - assert result.text == falcon.MEDIA_MSGPACK + assert result.text == response @pytest.mark.parametrize('mode', ['wsgi', 'asgi', 'asgi-stream']) From 03bb7c06b8ffcc9fcdb6a1c28cafcd3258838e67 Mon Sep 17 00:00:00 2001 From: Arthur Menezes Date: Tue, 7 Jan 2025 16:17:39 -0300 Subject: [PATCH 10/11] added and rendered towncrier docs --- docs/_newsfragments/1026.newandimproved.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/_newsfragments/1026.newandimproved.rst diff --git a/docs/_newsfragments/1026.newandimproved.rst b/docs/_newsfragments/1026.newandimproved.rst new file mode 100644 index 000000000..e55eb28d7 --- /dev/null +++ b/docs/_newsfragments/1026.newandimproved.rst @@ -0,0 +1,3 @@ +The :func:`~falcon.testing.simulate_request` now suports ``msgpack`` +and returns Content-Type as ``MEDIA_MSGPACK`` in a similar way that +was made to JSON parameters. \ No newline at end of file From 8fdf4aa45c5e878cd1bb47b2f287220deb053838 Mon Sep 17 00:00:00 2001 From: Arthur Menezes Date: Wed, 8 Jan 2025 15:50:11 -0300 Subject: [PATCH 11/11] added tests on content body --- tests/test_testing.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_testing.py b/tests/test_testing.py index a1ca63ab2..17cba8b36 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -154,6 +154,45 @@ def on_post(self, req, resp): assert result.text == response +@pytest.mark.skipif(not msgpack, reason='msgpack not installed') +@pytest.mark.parametrize( + 'value', + ( + 'd\xff\xff\x00', + 'quick fox jumps over the lazy dog', + '{"hello": "WORLD!"}', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praese', + '{"hello": "WORLD!", "greetings": "fellow traveller"}', + '\xe9\xe8', + ), +) +def test_simulate_request_msgpack_different_bodies(value): + value = bytes(value, 'UTF-8') + + resource = testing.SimpleTestResource(body=value) + + app = App() + app.add_route('/', resource) + + result = testing.simulate_post(app, '/', msgpack={}) + captured_resp = resource.captured_resp + content = captured_resp.text + + if len(value) > 40: + content = value[:20] + b'...' + value[-20:] + else: + content = value + + args = [ + captured_resp.status, + captured_resp.headers['content-type'], + str(content), + ] + + expected_content = 'Result<{}>'.format(' '.join(filter(None, args))) + assert str(result) == expected_content + + @pytest.mark.parametrize('mode', ['wsgi', 'asgi', 'asgi-stream']) def test_content_type(util, mode): class Responder: