diff --git a/docs/changes/4.1.0.rst b/docs/changes/4.1.0.rst index 5a9d8dc43..36f533cd5 100644 --- a/docs/changes/4.1.0.rst +++ b/docs/changes/4.1.0.rst @@ -14,9 +14,21 @@ 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 `__) + + Contributors to this Release ---------------------------- diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 77a16bf7e..cf1cdc47a 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -56,7 +56,9 @@ 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.media import MessagePackHandler from falcon.testing import helpers from falcon.testing.srmock import StartResponseMock from falcon.typing import Headers @@ -455,6 +457,7 @@ def simulate_request( content_type: Optional[str] = None, body: Optional[Union[str, bytes]] = None, json: Optional[Any] = None, + msgpack: Optional[Any] = None, file_wrapper: Optional[Callable[..., Any]] = None, wsgierrors: Optional[TextIO] = None, params: Optional[Mapping[str, Any]] = None, @@ -592,6 +595,7 @@ def simulate_request( content_type=content_type, body=body, json=json, + msgpack=msgpack, params=params, params_csv=params_csv, protocol=protocol, @@ -615,6 +619,7 @@ def simulate_request( headers, body, json, + msgpack, extras, ) @@ -724,6 +729,7 @@ async def _simulate_request_asgi( content_type: Optional[str] = None, body: Optional[Union[str, bytes]] = None, json: Optional[Any] = None, + msgpack: Optional[Any] = None, params: Optional[Mapping[str, Any]] = None, params_csv: bool = False, protocol: str = 'http', @@ -808,6 +814,12 @@ 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. 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 @@ -846,6 +858,7 @@ async def _simulate_request_asgi( headers, body, json, + msgpack, extras, ) @@ -1551,6 +1564,12 @@ 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. 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 @@ -1662,6 +1681,12 @@ 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. 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 @@ -1862,6 +1887,12 @@ 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. 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 @@ -1968,6 +1999,12 @@ 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. 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 @@ -2248,6 +2285,7 @@ def _prepare_sim_args( headers: Optional[HeaderArg], body: Optional[Union[str, bytes]], json: Optional[Any], + msgpack: Optional[Any], extras: Optional[Mapping[str, Any]], ) -> Tuple[ str, str, Optional[HeaderArg], Optional[Union[str, bytes]], Mapping[str, Any] @@ -2284,6 +2322,11 @@ def _prepare_sim_args( headers = dict(headers or {}) headers['Content-Type'] = MEDIA_JSON + if msgpack is not None: + body = MessagePackHandler().serialize(content_type=None, media=msgpack) + headers = 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 89a8c49e8..e24410069 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -103,6 +103,36 @@ 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 + + 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):