diff --git a/docs/_newsfragments/1853.breakingchange.rst b/docs/_newsfragments/1853.breakingchange.rst index e6183c030..9333b0104 100644 --- a/docs/_newsfragments/1853.breakingchange.rst +++ b/docs/_newsfragments/1853.breakingchange.rst @@ -1,3 +1,60 @@ -The deprecated ``has_representation()`` method for :class:`~falcon.HTTPError` was -removed, along with the ``NoRepresentation`` and ``OptionalRepresentation`` -classes. +A number of previously deprecated methods, attributes and classes have now been +removed: + +* In Falcon 3.0, the use of positional arguments was deprecated for the + optional initializer parameters of :class:`falcon.HTTPError` and its + subclasses. + + We have now redefined these optional arguments as keyword-only, so passing + them as positional arguments will result in a :class:`TypeError`: + + >>> import falcon + >>> falcon.HTTPForbidden('AccessDenied') + Traceback (most recent call last): + <...> + TypeError: HTTPForbidden.__init__() takes 1 positional argument but 2 were given + >>> falcon.HTTPForbidden('AccessDenied', 'No write access') + Traceback (most recent call last): + <...> + TypeError: HTTPForbidden.__init__() takes 1 positional argument but 3 were given + + Instead, simply pass these parameters as keyword arguments: + + >>> import falcon + >>> falcon.HTTPForbidden(title='AccessDenied') + + >>> falcon.HTTPForbidden(title='AccessDenied', description='No write access') + + +* The ``falcon-print-routes`` command-line utility is no longer supported; + ``falcon-inspect-app`` is a direct replacement. + +* A deprecated utility function, ``falcon.get_http_status()``, was removed. + Please use :meth:`falcon.code_to_http_status` instead. + +* A deprecated routing utility, ``compile_uri_template()``, was removed. + This function was only employed in the early versions of the framework, and + is expected to have been fully supplanted by the + :class:`~falcon.routing.CompiledRouter`. In a pinch, you can simply copy its + implementation from the Falcon 3.x source tree into your application. + +* The deprecated ``Response.add_link()`` method was removed; please use + :meth:`Response.append_link ` instead. + +* The deprecated ``has_representation()`` method for :class:`~falcon.HTTPError` + was removed, along with the ``NoRepresentation`` and + ``OptionalRepresentation`` classes. + +* An undocumented, deprecated public method ``find_by_media_type()`` of + :class:`media.Handlers ` was removed. + Apart from configuring handlers for Internet media types, the rest of + :class:`~falcon.media.Handlers` is only meant to be used internally by the + framework (unless documented otherwise). + +* Previously, the ``json`` module could be imported via ``falcon.util``. + This deprecated alias was removed; please import ``json`` directly from the + :mod:`standard library `, or another third-party JSON library of + choice. + +We decided, on the other hand, to keep the deprecated :class:`falcon.API` alias +until Falcon 5.0. diff --git a/docs/_newsfragments/2090.breakingchange.rst b/docs/_newsfragments/2090.breakingchange.rst index 9c5a677f2..6e16920ca 100644 --- a/docs/_newsfragments/2090.breakingchange.rst +++ b/docs/_newsfragments/2090.breakingchange.rst @@ -1,4 +1,5 @@ The deprecated ``api_helpers`` was removed in favor of the ``app_helpers`` module. In addition, the deprecated ``body`` -attributes for the :class:`~falcon.HTTPResponse`, -:class:`~falcon.asgi.HTTPResponse`, and :class:`~falcon.HTTPStatus` classes. +attributes for the :class:`~falcon.Response`, +:class:`asgi.Response `, +and :class:`~falcon.HTTPStatus` classes. diff --git a/docs/_newsfragments/2253.misc.rst b/docs/_newsfragments/2253.misc.rst index 256f4a736..178cc5494 100644 --- a/docs/_newsfragments/2253.misc.rst +++ b/docs/_newsfragments/2253.misc.rst @@ -1,3 +1,3 @@ The :ref:`utility functions ` ``create_task()`` and ``get_running_loop()`` are now deprecated in favor of their standard library -counterparts, :func:`asyncio.create_task` and `:func:`asyncio.get_running_loop`. +counterparts, :func:`asyncio.create_task` and :func:`asyncio.get_running_loop`. diff --git a/docs/api/routing.rst b/docs/api/routing.rst index 4d339c9da..c3a3fc41f 100644 --- a/docs/api/routing.rst +++ b/docs/api/routing.rst @@ -478,8 +478,6 @@ be used by custom routing engines. .. autofunction:: falcon.routing.set_default_responders -.. autofunction:: falcon.routing.compile_uri_template - .. autofunction:: falcon.app_helpers.prepare_middleware .. autofunction:: falcon.app_helpers.prepare_middleware_ws diff --git a/docs/api/util.rst b/docs/api/util.rst index 810790448..094bf4434 100644 --- a/docs/api/util.rst +++ b/docs/api/util.rst @@ -32,7 +32,6 @@ HTTP Status .. autofunction:: falcon.http_status_to_code .. autofunction:: falcon.code_to_http_status -.. autofunction:: falcon.get_http_status Media types ----------- diff --git a/falcon/__init__.py b/falcon/__init__.py index d6bcc1a06..542323b31 100644 --- a/falcon/__init__.py +++ b/falcon/__init__.py @@ -77,7 +77,6 @@ 'ETag', 'get_argnames', 'get_bound_method', - 'get_http_status', 'get_running_loop', 'http_cookies', 'http_date_to_dt', @@ -609,7 +608,6 @@ from falcon.util import ETag from falcon.util import get_argnames from falcon.util import get_bound_method -from falcon.util import get_http_status from falcon.util import get_running_loop from falcon.util import http_cookies from falcon.util import http_date_to_dt diff --git a/falcon/app.py b/falcon/app.py index 6ef6cda25..56ba35289 100644 --- a/falcon/app.py +++ b/falcon/app.py @@ -1263,7 +1263,7 @@ def _update_sink_and_static_routes(self) -> None: # TODO(myusko): This class is a compatibility alias, and should be removed -# in the next major release (4.0). +# in Falcon 5.0. class API(App): """Compatibility alias of :class:`falcon.App`. @@ -1271,12 +1271,12 @@ class API(App): reflect the breadth of applications that :class:`App `, and its ASGI counterpart in particular, can now be used for. - This compatibility alias should be considered deprecated; it will be - removed in a future release. + This compatibility alias is deprecated; it will be removed entirely in + Falcon 5.0. """ @deprecation.deprecated( - 'API class may be removed in a future release, use falcon.App instead.' + 'The API class will be removed in Falcon 5.0, use falcon.App instead.' ) def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) diff --git a/falcon/asgi/app.py b/falcon/asgi/app.py index 4478728c2..9b69eb58c 100644 --- a/falcon/asgi/app.py +++ b/falcon/asgi/app.py @@ -1058,7 +1058,7 @@ async def _call_lifespan_handlers( ) return - if self.req_options.auto_parse_form_urlencoded: + if self.req_options._auto_parse_form_urlencoded: await send( { 'type': EventType.LIFESPAN_STARTUP_FAILED, diff --git a/falcon/asgi/request.py b/falcon/asgi/request.py index f4f4f1d73..5fb622885 100644 --- a/falcon/asgi/request.py +++ b/falcon/asgi/request.py @@ -41,7 +41,7 @@ from falcon.typing import MISSING from falcon.typing import MissingOr from falcon.typing import StoreArgument -from falcon.util import deprecated +from falcon.util import deprecation from falcon.util import ETag from falcon.util.uri import parse_host from falcon.util.uri import parse_query_string @@ -348,8 +348,12 @@ def root_path(self) -> str: return '' @property - # NOTE(caselit): Deprecated long ago. Warns since 4.0 - @deprecated('Use `root_path` instead', is_property=True) + # NOTE(caselit): Deprecated long ago. Warns since 4.0. + @deprecation.deprecated( + 'Use `root_path` instead. ' + '(This compatibility alias will be removed in Falcon 5.0.)', + is_property=True, + ) def app(self) -> str: """Deprecated alias for :attr:`root_path`.""" return self.root_path diff --git a/falcon/cmd/inspect_app.py b/falcon/cmd/inspect_app.py index d1a1a038d..d48c0d30d 100644 --- a/falcon/cmd/inspect_app.py +++ b/falcon/cmd/inspect_app.py @@ -84,12 +84,13 @@ def load_app(parser, args): return app +# TODO(vytas): Remove this placeholder altogether in Falcon 5.0. def route_main(): - print( - 'The "falcon-print-routes" command is deprecated. ' - 'Please use "falcon-inspect-app"' + sys.stderr.write( + 'The "falcon-print-routes" command is no longer supported. \n\n' + 'Please use "falcon-inspect-app" instead.\n\n' ) - main() + sys.exit(2) def main(): diff --git a/falcon/errors.py b/falcon/errors.py index afc1faa8a..af64cf0f5 100644 --- a/falcon/errors.py +++ b/falcon/errors.py @@ -41,7 +41,6 @@ def on_get(self, req, resp): from falcon.http_error import HTTPError import falcon.status_codes as status -from falcon.util.deprecation import deprecated_args from falcon.util.misc import dt_to_http if TYPE_CHECKING: @@ -176,6 +175,40 @@ class WebSocketServerError(WebSocketDisconnected): HTTPErrorKeywordArguments = Union[str, int, None] + +# TODO(vytas): Passing **kwargs down to HTTPError results in arg-type error in +# Mypy, because it is impossible to verify that, e.g., an int value was not +# erroneously passed to href instead of code, etc. +# +# It is hard to properly type this on older Pythons, so we just sprinkle type +# ignores on the super().__init__(...) calls below. In any case, this call is +# internal to the framework. +# +# On Python 3.11+, I have verified it is possible to properly type this +# pattern using typing.Unpack: +# +# class HTTPErrorKeywordArguments(TypedDict): +# href: Optional[str] +# href_text: Optional[str] +# code: Optional[int] +# +# class HTTPErrorSubclass(HTTPError): +# def __init__( +# self, +# *, +# title: Optional[str] = None, +# description: Optional[str] = None, +# headers: Optional[HeaderList] = None, +# **kwargs: Unpack[HTTPErrorKeywordArguments], +# ) -> None: +# super().__init__( +# status.HTTP_400, +# title=title, +# description=description, +# headers=headers, +# **kwargs, +# ) + RetryAfter = Union[int, datetime, None] @@ -189,10 +222,7 @@ class HTTPBadRequest(HTTPError): (See also: RFC 7231, Section 6.5.1) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '400 Bad Request'). @@ -224,9 +254,9 @@ class HTTPBadRequest(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -237,7 +267,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -262,10 +292,7 @@ class HTTPUnauthorized(HTTPError): (See also: RFC 7235, Section 3.1) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '401 Unauthorized'). @@ -307,9 +334,9 @@ class HTTPUnauthorized(HTTPError): """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -325,7 +352,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -350,10 +377,7 @@ class HTTPForbidden(HTTPError): (See also: RFC 7231, Section 6.5.4) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '403 Forbidden'). @@ -385,9 +409,9 @@ class HTTPForbidden(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -398,7 +422,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -419,10 +443,7 @@ class HTTPNotFound(HTTPError): (See also: RFC 7231, Section 6.5.3) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Human-friendly error title. If not provided, and @@ -456,9 +477,9 @@ class HTTPNotFound(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -469,7 +490,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -483,10 +504,7 @@ class HTTPRouteNotFound(HTTPNotFound): behavior can be customized by registering a custom error handler for :class:`~.HTTPRouteNotFound`. - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Human-friendly error title. If not provided, and @@ -536,11 +554,8 @@ class HTTPMethodNotAllowed(HTTPError): (See also: RFC 7231, Section 6.5.5) - Note: - ``allowed_methods`` is the only positional argument allowed, the other - arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + `allowed_methods` is the only positional argument allowed, + the other arguments are defined as keyword-only. Args: allowed_methods (list of str): Allowed HTTP methods for this @@ -582,10 +597,10 @@ class HTTPMethodNotAllowed(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=1) def __init__( self, allowed_methods: Iterable[str], + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -598,7 +613,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -620,10 +635,7 @@ class HTTPNotAcceptable(HTTPError): (See also: RFC 7231, Section 6.5.6) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: description (str): Human-friendly description of the error, along with @@ -654,9 +666,9 @@ class HTTPNotAcceptable(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -667,7 +679,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -692,10 +704,7 @@ class HTTPConflict(HTTPError): (See also: RFC 7231, Section 6.5.8) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '409 Conflict'). @@ -727,9 +736,9 @@ class HTTPConflict(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -740,7 +749,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -769,10 +778,7 @@ class HTTPGone(HTTPError): (See also: RFC 7231, Section 6.5.9) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Human-friendly error title. If not provided, and @@ -806,9 +812,9 @@ class HTTPGone(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -819,7 +825,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -835,10 +841,7 @@ class HTTPLengthRequired(HTTPError): (See also: RFC 7231, Section 6.5.10) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '411 Length Required'). @@ -870,9 +873,9 @@ class HTTPLengthRequired(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -883,7 +886,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -900,10 +903,7 @@ class HTTPPreconditionFailed(HTTPError): (See also: RFC 7232, Section 4.2) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '412 Precondition Failed'). @@ -935,9 +935,9 @@ class HTTPPreconditionFailed(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -948,7 +948,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -967,10 +967,7 @@ class HTTPPayloadTooLarge(HTTPError): (See also: RFC 7231, Section 6.5.11) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '413 Payload Too Large'). @@ -1010,9 +1007,9 @@ class HTTPPayloadTooLarge(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, retry_after: RetryAfter = None, @@ -1024,7 +1021,7 @@ def __init__( title=title, description=description, headers=_parse_retry_after(headers, retry_after), - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1046,10 +1043,7 @@ class HTTPUriTooLong(HTTPError): (See also: RFC 7231, Section 6.5.12) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '414 URI Too Long'). @@ -1081,9 +1075,9 @@ class HTTPUriTooLong(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1094,7 +1088,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1111,10 +1105,7 @@ class HTTPUnsupportedMediaType(HTTPError): (See also: RFC 7231, Section 6.5.13) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '415 Unsupported Media Type'). @@ -1146,9 +1137,9 @@ class HTTPUnsupportedMediaType(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1159,7 +1150,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1180,11 +1171,8 @@ class HTTPRangeNotSatisfiable(HTTPError): (See also: RFC 7233, Section 4.4) - Note: - ``resource_length`` is the only positional argument allowed, the other - arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + `resource_length` is the only positional argument allowed, + the other arguments are defined as keyword-only. Args: resource_length: The maximum value for the last-byte-pos of a range @@ -1224,10 +1212,10 @@ class HTTPRangeNotSatisfiable(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=1) def __init__( self, resource_length: int, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1241,7 +1229,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1260,10 +1248,7 @@ class HTTPUnprocessableEntity(HTTPError): (See also: RFC 4918, Section 11.2) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '422 Unprocessable Entity'). @@ -1295,9 +1280,9 @@ class HTTPUnprocessableEntity(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1308,7 +1293,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1322,10 +1307,7 @@ class HTTPLocked(HTTPError): (See also: RFC 4918, Section 11.3) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '423 Locked'). @@ -1357,9 +1339,9 @@ class HTTPLocked(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1370,7 +1352,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1383,10 +1365,7 @@ class HTTPFailedDependency(HTTPError): (See also: RFC 4918, Section 11.4) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '424 Failed Dependency'). @@ -1418,9 +1397,9 @@ class HTTPFailedDependency(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1431,7 +1410,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1452,10 +1431,7 @@ class HTTPPreconditionRequired(HTTPError): (See also: RFC 6585, Section 3) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '428 Precondition Required'). @@ -1487,9 +1463,9 @@ class HTTPPreconditionRequired(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1500,7 +1476,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1518,10 +1494,7 @@ class HTTPTooManyRequests(HTTPError): (See also: RFC 6585, Section 4) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '429 Too Many Requests'). @@ -1561,9 +1534,9 @@ class HTTPTooManyRequests(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1575,7 +1548,7 @@ def __init__( title=title, description=description, headers=_parse_retry_after(headers, retry_after), - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1595,10 +1568,7 @@ class HTTPRequestHeaderFieldsTooLarge(HTTPError): (See also: RFC 6585, Section 5) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '431 Request Header Fields Too Large'). @@ -1630,9 +1600,9 @@ class HTTPRequestHeaderFieldsTooLarge(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1643,7 +1613,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1670,10 +1640,7 @@ class HTTPUnavailableForLegalReasons(HTTPError): (See also: RFC 7725, Section 3) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '451 Unavailable For Legal Reasons'). @@ -1705,9 +1672,9 @@ class HTTPUnavailableForLegalReasons(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1718,7 +1685,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1730,10 +1697,7 @@ class HTTPInternalServerError(HTTPError): (See also: RFC 7231, Section 6.6.1) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '500 Internal Server Error'). @@ -1766,9 +1730,9 @@ class HTTPInternalServerError(HTTPError): """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1779,7 +1743,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1797,10 +1761,7 @@ class HTTPNotImplemented(HTTPError): (See also: RFC 7231, Section 6.6.2) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '500 Internal Server Error'). @@ -1834,9 +1795,9 @@ class HTTPNotImplemented(HTTPError): """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1847,7 +1808,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1860,10 +1821,7 @@ class HTTPBadGateway(HTTPError): (See also: RFC 7231, Section 6.6.3) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '502 Bad Gateway'). @@ -1895,9 +1853,9 @@ class HTTPBadGateway(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1908,7 +1866,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -1929,10 +1887,7 @@ class HTTPServiceUnavailable(HTTPError): (See also: RFC 7231, Section 6.6.4) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '503 Service Unavailable'). @@ -1972,9 +1927,9 @@ class HTTPServiceUnavailable(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -1986,7 +1941,7 @@ def __init__( title=title, description=description, headers=_parse_retry_after(headers, retry_after), - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2000,10 +1955,7 @@ class HTTPGatewayTimeout(HTTPError): (See also: RFC 7231, Section 6.6.5) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '503 Service Unavailable'). @@ -2035,9 +1987,9 @@ class HTTPGatewayTimeout(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -2048,7 +2000,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2067,10 +2019,7 @@ class HTTPVersionNotSupported(HTTPError): (See also: RFC 7231, Section 6.6.6) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '503 Service Unavailable'). @@ -2102,9 +2051,9 @@ class HTTPVersionNotSupported(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -2115,7 +2064,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2132,10 +2081,7 @@ class HTTPInsufficientStorage(HTTPError): (See also: RFC 4918, Section 11.5) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '507 Insufficient Storage'). @@ -2167,9 +2113,9 @@ class HTTPInsufficientStorage(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -2180,7 +2126,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2194,10 +2140,7 @@ class HTTPLoopDetected(HTTPError): (See also: RFC 5842, Section 7.2) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '508 Loop Detected'). @@ -2229,9 +2172,9 @@ class HTTPLoopDetected(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -2242,7 +2185,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2268,10 +2211,7 @@ class HTTPNetworkAuthenticationRequired(HTTPError): (See also: RFC 6585, Section 6) - Note: - All the arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + All the arguments are defined as keyword-only. Keyword Args: title (str): Error title (default '511 Network Authentication Required'). @@ -2303,9 +2243,9 @@ class HTTPNetworkAuthenticationRequired(HTTPError): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=0) def __init__( self, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, @@ -2316,7 +2256,7 @@ def __init__( title=title, description=description, headers=headers, - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2325,11 +2265,8 @@ class HTTPInvalidHeader(HTTPBadRequest): One of the headers in the request is invalid. - Note: - ``msg`` and ``header_name`` are the only positional argument allowed, - the other arguments should be passed as keyword only. Using them as - positional arguments will raise a deprecation warning and will result - in an error in a future version of falcon. + `msg` and `header_name` are the only positional arguments allowed, + the other arguments are defined as keyword-only. Args: msg (str): A description of why the value is invalid. @@ -2362,11 +2299,11 @@ class HTTPInvalidHeader(HTTPBadRequest): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=2) def __init__( self, msg: str, header_name: str, + *, headers: Optional[HeaderList] = None, **kwargs: HTTPErrorKeywordArguments, ): @@ -2386,11 +2323,8 @@ class HTTPMissingHeader(HTTPBadRequest): A header is missing from the request. - Note: - ``header_name`` is the only positional argument allowed, the other - arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + `header_name` is the only positional argument allowed, + the other arguments are defined as keyword-only. Args: header_name (str): The name of the missing header. @@ -2422,10 +2356,10 @@ class HTTPMissingHeader(HTTPBadRequest): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=1) def __init__( self, header_name: str, + *, headers: Optional[HeaderList] = None, **kwargs: HTTPErrorKeywordArguments, ): @@ -2447,11 +2381,8 @@ class HTTPInvalidParam(HTTPBadRequest): parameter in a query string, form, or document that was submitted with the request. - Note: - ``msg`` and ``param_name`` are the only positional argument allowed, - the other arguments should be passed as keyword only. Using them as - positional arguments will raise a deprecation warning and will result - in an error in a future version of falcon. + `msg` and `param_name` are the only positional arguments allowed, + the other arguments are defined as keyword-only. Args: msg (str): A description of the invalid parameter. @@ -2484,11 +2415,11 @@ class HTTPInvalidParam(HTTPBadRequest): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=2) def __init__( self, msg: str, param_name: str, + *, headers: Optional[HeaderList] = None, **kwargs: HTTPErrorKeywordArguments, ) -> None: @@ -2510,11 +2441,8 @@ class HTTPMissingParam(HTTPBadRequest): parameter in a query string, form, or document that was submitted with the request. - Note: - ``param_name`` is the only positional argument allowed, the other - arguments should be passed as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + `param_name` is the only positional argument allowed, + the other arguments are defined as keyword-only. Args: param_name (str): The name of the missing parameter. @@ -2546,10 +2474,10 @@ class HTTPMissingParam(HTTPBadRequest): base articles related to this error (default ``None``). """ - @deprecated_args(allowed_positional=1) def __init__( self, param_name: str, + *, headers: Optional[HeaderList] = None, **kwargs: HTTPErrorKeywordArguments, ) -> None: @@ -2607,7 +2535,7 @@ def __init__(self, media_type: str, **kwargs: HTTPErrorKeywordArguments) -> None super().__init__( title='Invalid {0}'.format(media_type), description='Could not parse an empty {0} body'.format(media_type), - **kwargs, + **kwargs, # type: ignore[arg-type] ) @@ -2652,7 +2580,9 @@ def __init__( self, media_type: str, **kwargs: Union[HeaderList, HTTPErrorKeywordArguments] ): super().__init__( - title='Invalid {0}'.format(media_type), description=None, **kwargs + title='Invalid {0}'.format(media_type), + description=None, + **kwargs, # type: ignore[arg-type] ) self._media_type = media_type @@ -2749,9 +2679,9 @@ class MultipartParseError(MediaMalformedError): # NOTE(caselit): remove the description @property in MediaMalformedError description = None - @deprecated_args(allowed_positional=0) def __init__( self, + *, description: Optional[str] = None, **kwargs: Union[HeaderList, HTTPErrorKeywordArguments], ) -> None: @@ -2759,7 +2689,7 @@ def __init__( self, title='Malformed multipart/form-data request media', description=description, - **kwargs, + **kwargs, # type: ignore[arg-type] ) diff --git a/falcon/http_error.py b/falcon/http_error.py index 3f4af635c..51e837151 100644 --- a/falcon/http_error.py +++ b/falcon/http_error.py @@ -23,7 +23,6 @@ from falcon.util import code_to_http_status from falcon.util import http_status_to_code from falcon.util import uri -from falcon.util.deprecation import deprecated_args if TYPE_CHECKING: from falcon.media import BaseHandler @@ -49,11 +48,8 @@ class HTTPError(Exception): is implemented via ``to_dict()``). To also support XML, override the ``to_xml()`` method. - Note: - ``status`` is the only positional argument allowed, the other - arguments should be used as keyword only. Using them as positional - arguments will raise a deprecation warning and will result in an - error in a future version of falcon. + `status` is the only positional argument allowed, + the other arguments are defined as keyword-only. Args: status (Union[str,int]): HTTP status code or line (e.g., @@ -117,10 +113,10 @@ class HTTPError(Exception): 'code', ) - @deprecated_args(allowed_positional=1) def __init__( self, status: ResponseStatus, + *, title: Optional[str] = None, description: Optional[str] = None, headers: Optional[HeaderList] = None, diff --git a/falcon/http_status.py b/falcon/http_status.py index 1a591fcf4..dd5522777 100644 --- a/falcon/http_status.py +++ b/falcon/http_status.py @@ -18,7 +18,6 @@ from typing import Optional, TYPE_CHECKING from falcon.util import http_status_to_code -from falcon.util.deprecation import AttributeRemovedError if TYPE_CHECKING: from falcon.typing import HeaderList @@ -69,10 +68,3 @@ def __init__( def status_code(self) -> int: """HTTP status code normalized from :attr:`status`.""" return http_status_to_code(self.status) - - @property - def body(self) -> None: - raise AttributeRemovedError( - 'The body attribute is no longer supported. ' - 'Please use the text attribute instead.' - ) diff --git a/falcon/media/handlers.py b/falcon/media/handlers.py index 4eeaf4f7f..8c1965f06 100644 --- a/falcon/media/handlers.py +++ b/falcon/media/handlers.py @@ -30,7 +30,6 @@ from falcon.media.urlencoded import URLEncodedFormHandler from falcon.typing import DeserializeSync from falcon.typing import SerializeSync -from falcon.util import deprecation from falcon.util import misc from falcon.vendor import mimeparse @@ -180,35 +179,6 @@ def copy(self) -> Handlers: handlers_cls = type(self) return handlers_cls(self.data) - @deprecation.deprecated( - 'This undocumented method is no longer supported as part of the public ' - 'interface and will be removed in a future release.' - ) - def find_by_media_type( - self, media_type: Optional[str], default: str, raise_not_found: bool = True - ) -> Optional[BaseHandler]: - # PERF(jmvrbanac): Check via a quick methods first for performance - if media_type == '*/*' or not media_type: - media_type = default - - try: - return self.data[media_type] - except KeyError: - pass - - # PERF(jmvrbanac): Fallback to the slower method. - # NOTE(kgriffs): Wrap keys in a tuple to make them hashable. - resolved = _best_match(media_type, tuple(self.data.keys())) - - if not resolved: - if raise_not_found: - raise errors.HTTPUnsupportedMediaType( - description='{0} is an unsupported media type.'.format(media_type) - ) - return None - - return self.data[resolved] - def _best_match(media_type: str, all_media_types: Sequence[str]) -> Optional[str]: result = None diff --git a/falcon/request.py b/falcon/request.py index db7ad6ea4..6c4fd8b4c 100644 --- a/falcon/request.py +++ b/falcon/request.py @@ -35,6 +35,7 @@ Union, ) from uuid import UUID +import warnings from falcon import errors from falcon import request_helpers as helpers @@ -50,7 +51,7 @@ from falcon.typing import MissingOr from falcon.typing import ReadableIO from falcon.typing import StoreArgument -from falcon.util import deprecated +from falcon.util import deprecation from falcon.util import ETag from falcon.util import structures from falcon.util.uri import parse_host @@ -323,7 +324,7 @@ def __init__( # cycles and parse the content type for real, but # this heuristic will work virtually all the time. if ( - self.options.auto_parse_form_urlencoded + self.options._auto_parse_form_urlencoded and self.content_type is not None and 'application/x-www-form-urlencoded' in self.content_type and @@ -634,8 +635,12 @@ def root_path(self) -> str: return '' @property - # NOTE(caselit): Deprecated long ago. Warns since 4.0 - @deprecated('Use `root_path` instead', is_property=True) + # NOTE(caselit): Deprecated long ago. Warns since 4.0. + @deprecation.deprecated( + 'Use `root_path` instead. ' + '(This compatibility alias will be removed in Falcon 5.0.)', + is_property=True, + ) def app(self) -> str: """Deprecated alias for :attr:`root_path`.""" return self.root_path @@ -2411,37 +2416,58 @@ class RequestOptions: For comma-separated values, this option also determines whether or not empty elements in the parsed list are retained. """ - auto_parse_form_urlencoded: bool - """Set to ``True`` in order to automatically consume the request stream and merge - the results into the request's query string params when the request's content - type is ``application/x-www-form-urlencoded``` (default ``False``). - Enabling this option for WSGI apps makes the form parameters accessible via - :attr:`~falcon.Request.params`, :meth:`~falcon.Request.get_param`, etc. + @property + def auto_parse_form_urlencoded(self) -> bool: + """Set to ``True`` in order to automatically consume the request stream + and merge the results into the request's query string params when the + request's content type is ``application/x-www-form-urlencoded``` + (default ``False``). + + Enabling this option for WSGI apps makes the form parameters accessible + via :attr:`~falcon.Request.params`, :meth:`~falcon.Request.get_param`, + etc. - Warning: - The `auto_parse_form_urlencoded` option is not supported for - ASGI apps, and is considered deprecated for WSGI apps as of - Falcon 3.0, in favor of accessing URL-encoded forms - through :attr:`~Request.media`. + Warning: + The `auto_parse_form_urlencoded` option is not supported for + ASGI apps, and is considered deprecated for WSGI apps as of + Falcon 3.0, in favor of accessing URL-encoded forms + through :attr:`~Request.media`. - See also: :ref:`access_urlencoded_form` + The attribute and the auto-parsing functionality will be removed + entirely in Falcon 5.0. - Warning: - When this option is enabled, the request's body - stream will be left at EOF. The original data is - not retained by the framework. + See also: :ref:`access_urlencoded_form`. + + Warning: + When this option is enabled, the request's body + stream will be left at EOF. The original data is + not retained by the framework. + + Note: + The character encoding for fields, before + percent-encoding non-ASCII bytes, is assumed to be + UTF-8. The special `_charset_` field is ignored if + present. + + Falcon expects form-encoded request bodies to be + encoded according to the standard W3C algorithm (see + also https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#application%2Fx-www-form-urlencoded-encoding-algorithm). + """ # noqa: D205 + return self._auto_parse_form_urlencoded + + @auto_parse_form_urlencoded.setter + def auto_parse_form_urlencoded(self, value: bool) -> None: + if value: + warnings.warn( + 'The RequestOptions.auto_parse_form_urlencoded option is ' + 'deprecated. Please use Request.get_media() to consume ' + 'the submitted URL-encoded form instead.', + category=deprecation.DeprecatedWarning, + ) + + self._auto_parse_form_urlencoded = value - Note: - The character encoding for fields, before - percent-encoding non-ASCII bytes, is assumed to be - UTF-8. The special `_charset_` field is ignored if - present. - - Falcon expects form-encoded request bodies to be - encoded according to the standard W3C algorithm (see - also https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#application%2Fx-www-form-urlencoded-encoding-algorithm). - """ auto_parse_qs_csv: bool """Set to ``True`` to split query string values on any non-percent-encoded commas (default ``False``). @@ -2487,7 +2513,7 @@ class RequestOptions: __slots__ = ( 'keep_blank_qs_values', - 'auto_parse_form_urlencoded', + '_auto_parse_form_urlencoded', 'auto_parse_qs_csv', 'strip_url_path_trailing_slash', 'default_media_type', @@ -2496,7 +2522,7 @@ class RequestOptions: def __init__(self) -> None: self.keep_blank_qs_values = True - self.auto_parse_form_urlencoded = False + self._auto_parse_form_urlencoded = False self.auto_parse_qs_csv = False self.strip_url_path_trailing_slash = False self.default_media_type = DEFAULT_MEDIA_TYPE diff --git a/falcon/response.py b/falcon/response.py index 5c2a5c2f7..a8575970e 100644 --- a/falcon/response.py +++ b/falcon/response.py @@ -56,7 +56,6 @@ from falcon.util import http_status_to_code from falcon.util import structures from falcon.util.deprecation import AttributeRemovedError -from falcon.util.deprecation import deprecated from falcon.util.uri import encode_check_escaped as uri_encode from falcon.util.uri import encode_value_check_escaped as uri_encode_value @@ -210,20 +209,6 @@ def status_code(self) -> int: def status_code(self, value: int) -> None: self.status = value - @property - def body(self) -> NoReturn: - raise AttributeRemovedError( - 'The body attribute is no longer supported. ' - 'Please use the text attribute instead.' - ) - - @body.setter - def body(self, value: Any) -> NoReturn: - raise AttributeRemovedError( - 'The body attribute is no longer supported. ' - 'Please use the text attribute instead.' - ) - @property def data(self) -> Optional[bytes]: """Byte string representing response content. @@ -983,10 +968,12 @@ def append_link( else: _headers['link'] = value - # NOTE(kgriffs): Alias deprecated as of 3.0 - add_link = deprecated('Please use append_link() instead.', method_name='add_link')( - append_link - ) + @property + def add_link(self) -> NoReturn: + raise AttributeRemovedError( + 'The add_link() method is no longer supported. ' + 'Please use append_link() instead.' + ) cache_control: Union[str, Iterable[str], None] = header_property( 'Cache-Control', diff --git a/falcon/routing/__init__.py b/falcon/routing/__init__.py index 9db1c4862..802165702 100644 --- a/falcon/routing/__init__.py +++ b/falcon/routing/__init__.py @@ -29,7 +29,6 @@ from falcon.routing.converters import UUIDConverter from falcon.routing.static import StaticRoute from falcon.routing.static import StaticRouteAsync -from falcon.routing.util import compile_uri_template from falcon.routing.util import map_http_methods from falcon.routing.util import set_default_responders diff --git a/falcon/routing/util.py b/falcon/routing/util.py index b789b0829..eb709a442 100644 --- a/falcon/routing/util.py +++ b/falcon/routing/util.py @@ -16,12 +16,10 @@ from __future__ import annotations -import re -from typing import Optional, Set, Tuple, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from falcon import constants from falcon import responders -from falcon.util.deprecation import deprecated if TYPE_CHECKING: from falcon.typing import MethodDict @@ -33,78 +31,6 @@ def __init__(self, message: str) -> None: self.message = message -# NOTE(kgriffs): Published method; take care to avoid breaking changes. -@deprecated('This method will be removed in Falcon 4.0.') -def compile_uri_template(template: str) -> Tuple[Set[str], re.Pattern[str]]: - """Compile the given URI template string into a pattern matcher. - - This function can be used to construct custom routing engines that - iterate through a list of possible routes, attempting to match - an incoming request against each route's compiled regular expression. - - Each field is converted to a named group, so that when a match - is found, the fields can be easily extracted using - :meth:`re.MatchObject.groupdict`. - - This function does not support the more flexible templating - syntax used in the default router. Only simple paths with bracketed - field expressions are recognized. For example:: - - / - /books - /books/{isbn} - /books/{isbn}/characters - /books/{isbn}/characters/{name} - - Warning: - If the template contains a trailing slash character, it will be - stripped. - - Note that this is **different** from :ref:`the default behavior - ` of :func:`~falcon.App.add_route` used - with the default :class:`~falcon.routing.CompiledRouter`. - - The :attr:`~falcon.RequestOptions.strip_url_path_trailing_slash` - request option is not considered by ``compile_uri_template()``. - - - Args: - template(str): The template to compile. Note that field names are - restricted to ASCII a-z, A-Z, and the underscore character. - - Returns: - tuple: (template_field_names, template_regex) - - .. deprecated:: 3.1 - """ - - if not isinstance(template, str): - raise TypeError('uri_template is not a string') - - if not template.startswith('/'): - raise ValueError("uri_template must start with '/'") - - if '//' in template: - raise ValueError("uri_template may not contain '//'") - - if template != '/' and template.endswith('/'): - template = template[:-1] - - # template names should be able to start with A-Za-z - # but also contain 0-9_ in the remaining portion - expression_pattern = r'{([a-zA-Z]\w*)}' - - # Get a list of field names - fields = set(re.findall(expression_pattern, template)) - - # Convert Level 1 var patterns to equivalent named regex groups - escaped = re.sub(r'[\.\(\)\[\]\?\*\+\^\|]', r'\\\g<0>', template) - pattern = re.sub(expression_pattern, r'(?P<\1>[^/]+)', escaped) - pattern = r'\A' + pattern + r'\Z' - - return fields, re.compile(pattern, re.IGNORECASE) - - def map_http_methods(resource: object, suffix: Optional[str] = None) -> MethodDict: """Map HTTP methods (e.g., GET, POST) to methods of a resource object. diff --git a/falcon/util/__init__.py b/falcon/util/__init__.py index 03d810e9a..9c5ad4140 100644 --- a/falcon/util/__init__.py +++ b/falcon/util/__init__.py @@ -34,7 +34,6 @@ from falcon.util.misc import dt_to_http from falcon.util.misc import get_argnames from falcon.util.misc import get_bound_method -from falcon.util.misc import get_http_status from falcon.util.misc import http_date_to_dt from falcon.util.misc import http_now from falcon.util.misc import http_status_to_code @@ -80,18 +79,3 @@ BufferedReader = ( (_CyBufferedReader or _PyBufferedReader) if IS_64_BITS else _PyBufferedReader ) - - -def __getattr__(name: str) -> ModuleType: - if name == 'json': - import json # NOQA - import warnings - - warnings.warn( - 'Importing json from "falcon.util" is deprecated.', DeprecatedWarning - ) - return json - - # fallback to the default implementation - mod = sys.modules[__name__] - return ModuleType.__getattr__(mod, name) diff --git a/falcon/util/misc.py b/falcon/util/misc.py index 1fbe09ef3..051d62698 100644 --- a/falcon/util/misc.py +++ b/falcon/util/misc.py @@ -55,7 +55,6 @@ 'to_query_str', 'get_bound_method', 'get_argnames', - 'get_http_status', 'http_status_to_code', 'code_to_http_status', 'secure_filename', @@ -331,47 +330,6 @@ def get_argnames(func: Callable[..., Any]) -> List[str]: return args -@deprecated('Please use falcon.code_to_http_status() instead.') -def get_http_status( - status_code: Union[str, int], default_reason: str = _DEFAULT_HTTP_REASON -) -> str: - """Get both the http status code and description from just a code. - - Warning: - As of Falcon 3.0, this method has been deprecated in favor of - :meth:`~falcon.code_to_http_status`. - - Args: - status_code: integer or string that can be converted to an integer - default_reason: default text to be appended to the status_code - if the lookup does not find a result - - Returns: - str: status code e.g. "404 Not Found" - - Raises: - ValueError: the value entered could not be converted to an integer - - """ - # sanitize inputs - try: - code = float(status_code) # float can validate values like "401.1" - code = int(code) # converting to int removes the decimal places - if code < 100: - raise ValueError - except ValueError: - raise ValueError( - 'get_http_status failed: "%s" is not a valid status code', status_code - ) - - # lookup the status code - try: - return getattr(status_codes, 'HTTP_' + str(code)) - except AttributeError: - # not found - return str(code) + ' ' + default_reason - - def secure_filename(filename: Optional[str]) -> str: """Sanitize the provided `filename` to contain only ASCII characters. @@ -461,9 +419,8 @@ def code_to_http_status(status: Union[int, http.HTTPStatus, bytes, str]) -> str: An LRU is used to minimize lookup time. Note: - Unlike the deprecated :func:`get_http_status`, this function will not - attempt to coerce a string status to an integer code, assuming the - string already denotes an HTTP status line. + This function will not attempt to coerce a string status to an + integer code, assuming the string already denotes an HTTP status line. Args: status: The status code or enum to normalize. @@ -519,6 +476,6 @@ def _encode_items_to_latin1(data: Dict[str, str]) -> List[Tuple[bytes, bytes]]: _encode_items_to_latin1 = _cy_encode_items_to_latin1 or _encode_items_to_latin1 -isascii = deprecated('This will be removed in V5. Please use `str.isascii`')( - str.isascii -) +isascii = deprecated( + 'This method will be removed in Falcon 5.0; please use str.isascii() instead.' +)(str.isascii) diff --git a/falcon/util/sync.py b/falcon/util/sync.py index 0bfc24021..6d9d4e45f 100644 --- a/falcon/util/sync.py +++ b/falcon/util/sync.py @@ -6,7 +6,7 @@ import os from typing import Any, Awaitable, Callable, Optional, TypeVar, Union -from falcon.util import deprecated +from falcon.util import deprecation __all__ = ( 'async_to_sync', @@ -56,11 +56,13 @@ def __call__(self) -> _DummyRunner: _active_runner = _ActiveRunner(getattr(asyncio, 'Runner', _DummyRunner)) _one_thread_to_rule_them_all = ThreadPoolExecutor(max_workers=1) -create_task = deprecated( - 'This will be removed in V5. Please use `asyncio.create_task`' +create_task = deprecation.deprecated( + 'This alias is deprecated; it will be removed in Falcon 5.0. ' + 'Please use asyncio.create_task() directly.' )(asyncio.create_task) -get_running_loop = deprecated( - 'This will be removed in V5. Please use `asyncio.get_running_loop`' +get_running_loop = deprecation.deprecated( + 'This alias is deprecated; it will be removed in Falcon 5.0. ' + 'Please use asyncio.get_running_loop() directly.' )(asyncio.get_running_loop) diff --git a/falcon/util/time.py b/falcon/util/time.py index 6e4cce993..952cbb3bf 100644 --- a/falcon/util/time.py +++ b/falcon/util/time.py @@ -22,7 +22,10 @@ class TimezoneGMT(datetime.tzinfo): GMT_ZERO = datetime.timedelta(hours=0) - @deprecated('TimezoneGMT is deprecated, use datetime.timezone.utc instead') + @deprecated( + 'TimezoneGMT is deprecated, use datetime.timezone.utc instead. ' + '(TimezoneGMT will be removed in Falcon 5.0.)' + ) def __init__(self) -> None: super().__init__() diff --git a/tests/_wsgi_test_app.py b/tests/_wsgi_test_app.py index aa565ef66..f7eb6e9f8 100644 --- a/tests/_wsgi_test_app.py +++ b/tests/_wsgi_test_app.py @@ -41,7 +41,9 @@ def on_get_deprecated(self, req, resp): resp.set_header('X-Falcon', 'deprecated') resp.content_type = falcon.MEDIA_TEXT - resp.body = 'Hello, World!\n' + resp.text = 'Hello, World!\n' + + resp.add_link('/removed-methods/add-link', 'bookmark') app = application = falcon.App() diff --git a/tests/asgi/test_asgi_servers.py b/tests/asgi/test_asgi_servers.py index aef045d05..eb35ac62d 100644 --- a/tests/asgi/test_asgi_servers.py +++ b/tests/asgi/test_asgi_servers.py @@ -250,9 +250,9 @@ async def test_hello( message_binary = await ws.recv() except websockets.exceptions.ConnectionClosed as ex: if explicit_close and close_code: - assert ex.code == close_code + assert ex.rcvd.code == close_code else: - assert ex.code == 1000 + assert ex.rcvd.code == 1000 break diff --git a/tests/asgi/test_response_media_asgi.py b/tests/asgi/test_response_media_asgi.py index a0a64f9d9..654536291 100644 --- a/tests/asgi/test_response_media_asgi.py +++ b/tests/asgi/test_response_media_asgi.py @@ -7,7 +7,6 @@ from falcon import media from falcon import testing import falcon.asgi -from falcon.util.deprecation import DeprecatedWarning try: import msgpack @@ -182,8 +181,7 @@ async def on_get(self, req, resp): assert result.json == doc -@pytest.mark.parametrize('monkeypatch_resolver', [True, False]) -def test_mimeparse_edgecases(monkeypatch_resolver): +def test_mimeparse_edgecases(): doc = {'something': True} class TestResource: @@ -205,21 +203,6 @@ async def on_get(self, req, resp): client = create_client(TestResource()) - handlers = client.app.resp_options.media_handlers - - # NOTE(kgriffs): Test the pre-3.0 method. Although undocumented, it was - # technically a public method, and so we make sure it still works here. - if monkeypatch_resolver: - - def _resolve(media_type, default, raise_not_found=True): - with pytest.warns(DeprecatedWarning, match='This undocumented method'): - h = handlers.find_by_media_type( - media_type, default, raise_not_found=raise_not_found - ) - return h, None, None - - handlers._resolve = _resolve - result = client.simulate_get('/') assert result.json == doc diff --git a/tests/test_alias.py b/tests/test_alias.py index ffb1df184..d38d3d78f 100644 --- a/tests/test_alias.py +++ b/tests/test_alias.py @@ -15,7 +15,7 @@ def on_get(self, req, resp): @pytest.fixture def alias_client(): - with pytest.warns(DeprecatedWarning, match='API class may be removed'): + with pytest.warns(DeprecatedWarning, match='API class will be removed'): api = falcon.API() api.add_route('/get-cookie', CookieResource()) return testing.TestClient(api) @@ -42,6 +42,6 @@ def test_cookies(alias_client, app_client): def test_alias_equals_to_app(alias_client): - with pytest.warns(DeprecatedWarning, match='API class may be removed'): + with pytest.warns(DeprecatedWarning, match='API class will be removed'): api = falcon.API() assert isinstance(api, falcon.API) diff --git a/tests/test_cmd_inspect_app.py b/tests/test_cmd_inspect_app.py index 837849520..0856df443 100644 --- a/tests/test_cmd_inspect_app.py +++ b/tests/test_cmd_inspect_app.py @@ -198,8 +198,9 @@ def mock(): monkeypatch.setattr(inspect_app, 'main', mock) output = io.StringIO() - with redirected(stdout=output): - inspect_app.route_main() + with redirected(stderr=output): + with pytest.raises(SystemExit): + inspect_app.route_main() - assert 'deprecated' in output.getvalue() - assert called + assert 'no longer supported' in output.getvalue() + assert not called diff --git a/tests/test_error.py b/tests/test_error.py index a3665f664..f1e2e40c6 100644 --- a/tests/test_error.py +++ b/tests/test_error.py @@ -3,7 +3,6 @@ import falcon import falcon.errors as errors import falcon.status_codes as status -from falcon.util.deprecation import DeprecatedWarning @pytest.mark.parametrize( @@ -151,10 +150,7 @@ def test_with_title_desc_and_headers(err): ], ) def test_kw_only(err): - # only deprecated for now - # with pytest.raises(TypeError, match='positional argument'): - # err('foo', 'bar') - with pytest.warns(DeprecatedWarning, match='positional args are deprecated'): + with pytest.raises(TypeError, match='positional argument'): err('foo', 'bar') @@ -190,10 +186,7 @@ def test_with_title_desc_and_headers_args(err, args): ), ) def test_args_kw_only(err, args): - # only deprecated for now - # with pytest.raises(TypeError, match='positional argument'): - # err(*args, 'bar') - with pytest.warns(DeprecatedWarning, match='positional args are deprecated'): + with pytest.raises(TypeError, match='positional argument'): err(*args, 'bar') diff --git a/tests/test_headers.py b/tests/test_headers.py index f7ba41d72..871e08960 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -867,12 +867,12 @@ def test_append_link_multiple(self, client): uri = 'ab\u00e7' resource = LinkHeaderResource() - resource.add_link('/things/2842', 'next') + resource.append_link('/things/2842', 'next') resource.append_link('http://\u00e7runchy/bacon', 'contents') resource.append_link(uri, 'http://example.com/ext-type') - resource.add_link(uri, 'http://example.com/\u00e7runchy') + resource.append_link(uri, 'http://example.com/\u00e7runchy') resource.append_link(uri, 'https://example.com/too-\u00e7runchy') - resource.add_link('/alt-thing', 'alternate http://example.com/\u00e7runchy') + resource.append_link('/alt-thing', 'alternate http://example.com/\u00e7runchy') self._check_link_header(client, resource, expected_value) diff --git a/tests/test_httperror.py b/tests/test_httperror.py index a3d68724f..745286b4e 100644 --- a/tests/test_httperror.py +++ b/tests/test_httperror.py @@ -8,7 +8,6 @@ import falcon import falcon.testing as testing -from falcon.util.deprecation import DeprecatedWarning try: import yaml @@ -946,8 +945,5 @@ def test_MediaMalformedError(self): def test_kw_only(): - # only deprecated for now - # with pytest.raises(TypeError, match='positional argument'): - # falcon.HTTPError(falcon.HTTP_BAD_REQUEST, 'foo', 'bar') - with pytest.warns(DeprecatedWarning, match='positional args are deprecated'): + with pytest.raises(TypeError, match='positional argument'): falcon.HTTPError(falcon.HTTP_BAD_REQUEST, 'foo', 'bar') diff --git a/tests/test_httpstatus.py b/tests/test_httpstatus.py index ed17d0095..4bbaec36c 100644 --- a/tests/test_httpstatus.py +++ b/tests/test_httpstatus.py @@ -5,7 +5,6 @@ import falcon from falcon.http_status import HTTPStatus import falcon.testing as testing -from falcon.util.deprecation import AttributeRemovedError @pytest.fixture() @@ -277,6 +276,3 @@ def test_deprecated_body(): sts = HTTPStatus(falcon.HTTP_701, text='foo') assert sts.text == 'foo' - - with pytest.raises(AttributeRemovedError): - assert sts.body == 'foo' diff --git a/tests/test_media_handlers.py b/tests/test_media_handlers.py index e008248f5..020055300 100644 --- a/tests/test_media_handlers.py +++ b/tests/test_media_handlers.py @@ -9,7 +9,6 @@ from falcon import media from falcon import testing from falcon.asgi.stream import BoundedStream -from falcon.util.deprecation import DeprecatedWarning mujson = None orjson = None @@ -202,7 +201,6 @@ def on_get(self, req, res): assert res.json == data -@pytest.mark.parametrize('monkeypatch_resolver', [True, False]) @pytest.mark.parametrize( 'handler_mt', [ @@ -211,7 +209,7 @@ def on_get(self, req, res): 'application/json; answer=42', ], ) -def test_deserialization_raises(asgi, util, handler_mt, monkeypatch_resolver): +def test_deserialization_raises(asgi, util, handler_mt): app = util.create_app(asgi) class SuchException(Exception): @@ -229,19 +227,6 @@ def serialize(self, media, content_type): handlers = media.Handlers({handler_mt: FaultyHandler()}) - # NOTE(kgriffs): Test the pre-3.0 method. Although undocumented, it was - # technically a public method, and so we make sure it still works here. - if monkeypatch_resolver: - - def _resolve(media_type, default, raise_not_found=True): - with pytest.warns(DeprecatedWarning, match='This undocumented method'): - h = handlers.find_by_media_type( - media_type, default, raise_not_found=raise_not_found - ) - return h, None, None - - handlers._resolve = _resolve - app.req_options.media_handlers = handlers app.resp_options.media_handlers = handlers @@ -368,25 +353,11 @@ async def on_post(self, req, resp): assert result.json == [None] -@pytest.mark.parametrize('monkeypatch_resolver', [True, False]) -def test_json_err_no_handler(asgi, util, monkeypatch_resolver): +def test_json_err_no_handler(asgi, util): app = util.create_app(asgi) handlers = media.Handlers({falcon.MEDIA_URLENCODED: media.URLEncodedFormHandler()}) - # NOTE(kgriffs): Test the pre-3.0 method. Although undocumented, it was - # technically a public method, and so we make sure it still works here. - if monkeypatch_resolver: - - def _resolve(media_type, default, raise_not_found=True): - with pytest.warns(DeprecatedWarning, match='This undocumented method'): - h = handlers.find_by_media_type( - media_type, default, raise_not_found=raise_not_found - ) - return h, None, None - - handlers._resolve = _resolve - app.req_options.media_handlers = handlers app.resp_options.media_handlers = handlers diff --git a/tests/test_options.py b/tests/test_options.py index 2bb48c10e..dcd32cf93 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -1,6 +1,7 @@ import pytest from falcon.request import RequestOptions +from falcon.util import deprecation class TestRequestOptions: @@ -24,7 +25,11 @@ def test_option_defaults(self): def test_options_toggle(self, option_name): options = RequestOptions() - setattr(options, option_name, True) + if option_name == 'auto_parse_form_urlencoded': + with pytest.warns(deprecation.DeprecatedWarning): + setattr(options, option_name, True) + else: + setattr(options, option_name, True) assert getattr(options, option_name) setattr(options, option_name, False) diff --git a/tests/test_query_params.py b/tests/test_query_params.py index 414a58661..93acbddf6 100644 --- a/tests/test_query_params.py +++ b/tests/test_query_params.py @@ -8,6 +8,7 @@ import falcon from falcon.errors import HTTPInvalidParam import falcon.testing as testing +from falcon.util import deprecation class Resource(testing.SimpleTestResource): @@ -46,7 +47,8 @@ def resource(): def client(asgi, util): app = util.create_app(asgi) if not asgi: - app.req_options.auto_parse_form_urlencoded = True + with pytest.warns(deprecation.DeprecatedWarning): + app.req_options.auto_parse_form_urlencoded = True return testing.TestClient(app) @@ -1019,7 +1021,8 @@ def test_explicitly_disable_auto_parse(self, client, resource): def test_asgi_raises_error(self, util, resource): app = util.create_app(asgi=True) app.add_route('/', resource) - app.req_options.auto_parse_form_urlencoded = True + with pytest.warns(deprecation.DeprecatedWarning): + app.req_options.auto_parse_form_urlencoded = True with pytest.raises(RuntimeError) as exc_info: testing.simulate_get(app, '/') diff --git a/tests/test_response.py b/tests/test_response.py index 7fac6b006..d986c7af9 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -5,6 +5,7 @@ from falcon import MEDIA_TEXT from falcon import ResponseOptions +from falcon.util.deprecation import AttributeRemovedError @pytest.fixture() @@ -39,6 +40,13 @@ def test_response_get_headers(resp): assert 'set-cookie' not in headers +def test_add_link_removed(resp): + # NOTE(kgriffs): Ensure AttributeRemovedError inherits from AttributeError + for exc_type in (AttributeError, AttributeRemovedError): + with pytest.raises(exc_type): + resp.add_link('/things/1337', 'next') + + def test_response_attempt_to_set_read_only_headers(resp): resp.append_header('x-things1', 'thing-1') resp.append_header('x-things2', 'thing-2') diff --git a/tests/test_response_body.py b/tests/test_response_body.py index 2326389b0..7a6b41f6d 100644 --- a/tests/test_response_body.py +++ b/tests/test_response_body.py @@ -2,7 +2,6 @@ import falcon from falcon import testing -from falcon.util.deprecation import AttributeRemovedError @pytest.fixture @@ -14,20 +13,12 @@ def test_append_body(resp): text = 'Hello beautiful world! ' resp.text = '' - with pytest.raises(AttributeRemovedError): - resp.body = 'x' - for token in text.split(): resp.text += token resp.text += ' ' assert resp.text == text - # NOTE(kgriffs): Ensure AttributeRemovedError inherits from AttributeError - for ErrorType in (AttributeError, AttributeRemovedError): - with pytest.raises(ErrorType): - resp.body - def test_response_repr(resp): _repr = '<%s: %s>' % (resp.__class__.__name__, resp.status) diff --git a/tests/test_uri_templates_legacy.py b/tests/test_uri_templates_legacy.py deleted file mode 100644 index e7a19eac6..000000000 --- a/tests/test_uri_templates_legacy.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest - -import falcon -from falcon import routing -from falcon.util.deprecation import DeprecatedWarning - - -@pytest.mark.filterwarnings('ignore:Call to deprecated function compile_uri_template') -class TestUriTemplates: - @pytest.mark.parametrize('value', (42, falcon.App)) - def test_string_type_required(self, value): - with pytest.raises(TypeError): - routing.compile_uri_template(value) - - @pytest.mark.parametrize('value', ('this', 'this/that')) - def test_template_must_start_with_slash(self, value): - with pytest.raises(ValueError): - routing.compile_uri_template(value) - - @pytest.mark.parametrize('value', ('//', 'a//', '//b', 'a//b', 'a/b//', 'a/b//c')) - def test_template_may_not_contain_double_slash(self, value): - with pytest.raises(ValueError): - routing.compile_uri_template(value) - - def test_root(self): - fields, pattern = routing.compile_uri_template('/') - assert not fields - assert not pattern.match('/x') - - result = pattern.match('/') - assert result - assert not result.groupdict() - - @pytest.mark.parametrize( - 'path', ('/hello', '/hello/world', '/hi/there/how/are/you') - ) - def test_no_fields(self, path): - fields, pattern = routing.compile_uri_template(path) - assert not fields - assert not pattern.match(path[:-1]) - - result = pattern.match(path) - assert result - assert not result.groupdict() - - def test_one_field(self): - fields, pattern = routing.compile_uri_template('/{name}') - assert fields == {'name'} - - result = pattern.match('/Kelsier') - assert result - assert result.groupdict() == {'name': 'Kelsier'} - - fields, pattern = routing.compile_uri_template('/character/{name}') - assert fields == {'name'} - - result = pattern.match('/character/Kelsier') - assert result - assert result.groupdict() == {'name': 'Kelsier'} - - fields, pattern = routing.compile_uri_template('/character/{name}/profile') - assert fields == {'name'} - - assert not pattern.match('/character') - assert not pattern.match('/character/Kelsier') - assert not pattern.match('/character/Kelsier/') - - result = pattern.match('/character/Kelsier/profile') - assert result - assert result.groupdict() == {'name': 'Kelsier'} - - def test_one_field_with_digits(self): - fields, pattern = routing.compile_uri_template('/{name123}') - assert fields == {'name123'} - - result = pattern.match('/Kelsier') - assert result - assert result.groupdict() == {'name123': 'Kelsier'} - - def test_one_field_with_prefixed_digits(self): - fields, pattern = routing.compile_uri_template('/{37signals}') - assert fields == set() - - result = pattern.match('/s2n') - assert not result - - @pytest.mark.parametrize('postfix', ('', '/')) - def test_two_fields(self, postfix): - path = '/book/{book_id}/characters/{n4m3}' + postfix - fields, pattern = routing.compile_uri_template(path) - assert fields == {'n4m3', 'book_id'} - - result = pattern.match('/book/0765350386/characters/Vin') - assert result - assert result.groupdict() == {'n4m3': 'Vin', 'book_id': '0765350386'} - - def test_three_fields(self): - fields, pattern = routing.compile_uri_template('/{a}/{b}/x/{c}') - assert fields == set('abc') - - result = pattern.match('/one/2/x/3') - assert result - assert result.groupdict() == {'a': 'one', 'b': '2', 'c': '3'} - - def test_malformed_field(self): - fields, pattern = routing.compile_uri_template('/{a}/{1b}/x/{c}') - assert fields == set('ac') - - result = pattern.match('/one/{1b}/x/3') - assert result - assert result.groupdict() == {'a': 'one', 'c': '3'} - - def test_deprecated_warning(self): - with pytest.warns( - DeprecatedWarning, - match='Call to deprecated function compile_uri_template().', - ): - routing.compile_uri_template('/') diff --git a/tests/test_utils.py b/tests/test_utils.py index 159fdd9a7..14da664f7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -493,34 +493,6 @@ def test_parse_host(self): assert uri.parse_host('falcon.example.com:9876') == ('falcon.example.com', 9876) assert uri.parse_host('falcon.example.com:42') == ('falcon.example.com', 42) - def test_get_http_status_warns(self): - with pytest.warns(UserWarning, match='Please use falcon'): - falcon.get_http_status(400) - - @pytest.mark.filterwarnings('ignore') - def test_get_http_status(self): - assert falcon.get_http_status(404) == falcon.HTTP_404 - assert falcon.get_http_status(404.3) == falcon.HTTP_404 - assert falcon.get_http_status('404.3') == falcon.HTTP_404 - assert falcon.get_http_status(404.9) == falcon.HTTP_404 - assert falcon.get_http_status('404') == falcon.HTTP_404 - assert falcon.get_http_status(123) == '123 Unknown' - with pytest.raises(ValueError): - falcon.get_http_status('not_a_number') - with pytest.raises(ValueError): - falcon.get_http_status(0) - with pytest.raises(ValueError): - falcon.get_http_status(0) - with pytest.raises(ValueError): - falcon.get_http_status(99) - with pytest.raises(ValueError): - falcon.get_http_status(-404.3) - with pytest.raises(ValueError): - falcon.get_http_status('-404') - with pytest.raises(ValueError): - falcon.get_http_status('-404.3') - assert falcon.get_http_status(123, 'Go Away') == '123 Go Away' - @pytest.mark.parametrize( 'v_in,v_out', [ @@ -1214,7 +1186,7 @@ def test_something(self): class TestSetupApi(testing.TestCase): def setUp(self): super(TestSetupApi, self).setUp() - with pytest.warns(UserWarning, match='API class may be removed in a future'): + with pytest.warns(UserWarning, match='API class will be removed in Falcon 5.0'): self.app = falcon.API() self.app.add_route('/', testing.SimpleTestResource(body='test')) @@ -1419,14 +1391,6 @@ def a_function(a=1, b=2): assert 'a_function(...)' in str(recwarn[0].message) -def test_json_deprecation(): - with pytest.warns(deprecation.DeprecatedWarning, match='json'): - falcon.util.json - - with pytest.raises(AttributeError): - falcon.util.some_imaginary_module - - def test_TimezoneGMT(): with pytest.warns(deprecation.DeprecatedWarning): tz = TimezoneGMT() diff --git a/tests/test_wsgi_servers.py b/tests/test_wsgi_servers.py index e8f00892c..e9f4520c8 100644 --- a/tests/test_wsgi_servers.py +++ b/tests/test_wsgi_servers.py @@ -208,7 +208,7 @@ def test_get(self, server_url): def test_get_deprecated(self, server_url): resp = requests.get(server_url + '/deprecated', timeout=_REQUEST_TIMEOUT) - # Since it tries to set .body we expect an unhandled error + # Since it tries to use resp.add_link() we expect an unhandled error assert resp.status_code == 500 def test_post_multipart_form(self, server_url):