From 4ac5ec6ff6c7502873bdb3b4d79af5c960126028 Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Wed, 6 Nov 2024 19:29:37 +0100 Subject: [PATCH] chore: prepare Falcon 4.0.2 (in-tree) (#2392) * chore: prepare 4.0.2 in-tree (WiP) * chore: relabel #2387 as bugfix * chore: aggregate 4.0.2 contributors * chore: bump up version to `4.0.2` * docs: slightly tweak newsfragment for #2387 * chore: aggregate contributors, +docs tweaks * docs(ASGI): clean up docs on logging setup * chore: render out newsfragments * docs(typing); describe another limitation/inconsistency --- AUTHORS | 3 + docs/_newsfragments/2365.misc.rst | 1 - docs/_newsfragments/2387.misc.rst | 7 -- docs/api/typing.rst | 16 +++-- docs/changes/4.0.2.rst | 38 +++++++++++ docs/changes/4.1.0.rst | 25 ------- docs/changes/index.rst | 2 +- docs/user/faq.rst | 49 +++++++------ docs/user/tutorial-asgi.rst | 110 +++++++++++++++++------------- falcon/typing.py | 17 ++++- falcon/version.py | 2 +- pyproject.toml | 2 +- 12 files changed, 155 insertions(+), 117 deletions(-) delete mode 100644 docs/_newsfragments/2365.misc.rst delete mode 100644 docs/_newsfragments/2387.misc.rst create mode 100644 docs/changes/4.0.2.rst delete mode 100644 docs/changes/4.1.0.rst diff --git a/AUTHORS b/AUTHORS index 95b44b1fd..9c1976cb3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -199,6 +199,9 @@ listed below by date of first contribution: * Agustin Arce (aarcex3) * Christian Grossmüller (chgad) * Sai Prathik R (prathik2401) +* Akshay Awate (AkshayAwate) +* Jasper Spaans (jap) +* Alessandro Chitarrini (chitvs) (et al.) diff --git a/docs/_newsfragments/2365.misc.rst b/docs/_newsfragments/2365.misc.rst deleted file mode 100644 index e379cb63a..000000000 --- a/docs/_newsfragments/2365.misc.rst +++ /dev/null @@ -1 +0,0 @@ -The printable PDF version of our documentation was enabled on Read the Docs. 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/api/typing.rst b/docs/api/typing.rst index 5bca16c44..3dab9abfe 100644 --- a/docs/api/typing.rst +++ b/docs/api/typing.rst @@ -33,11 +33,11 @@ Known Limitations Falcon's emphasis on flexibility and performance presents certain challenges when it comes to adding type annotations to the existing code base. + One notable limitation involves using custom :class:`~falcon.Request` and/or :class:`~falcon.Response` types in callbacks that are passed back to the framework, such as when adding an :meth:`error handler `. - For instance, the following application might unexpectedly not pass type checking: @@ -60,12 +60,20 @@ checking: app = App(request_type=MyRequest) app.add_error_handler(OSError, handle_os_error) -(Please also see the following GitHub issue: +(We are working on addressing this limitation at the time of writing -- +please see the following GitHub issue for the progress, and possible solutions: `#2372 `__.) +Another known inconsistency is the typing of the +:class:`converter interface `, where certain +subclasses (such as :class:`~falcon.routing.PathConverter`) declare a different +input type than the base ``convert()`` method. +(See also the discussions and possible solutions on the GitHub issue +`#2396 `__.) + .. important:: - This is only a typing limitation that has no effect outside of type - checking -- the above ``app`` will run just fine! + The above issues are only typing limitations that have no effect outside of + type checking -- applications will work just fine at runtime! Public Type Aliases diff --git a/docs/changes/4.0.2.rst b/docs/changes/4.0.2.rst new file mode 100644 index 000000000..521c3d591 --- /dev/null +++ b/docs/changes/4.0.2.rst @@ -0,0 +1,38 @@ +Changelog for Falcon 4.0.2 +========================== + +Summary +------- + +This is a minor point release to fix some missed re-exports for type checkers. +In addition, we have also included a couple of documentation improvements. + + +Fixed +----- + +- Running Mypy on code that uses parts of ``falcon.testing`` + would previously lead to errors like:: + + Name "falcon.testing.TestClient" is not defined + + This has been fixed by explicitly exporting the names that are + imported into the ``falcon.testing`` namespace. (`#2387 `__) + + +Misc +---- + +- The printable PDF version of our documentation was enabled on Read the Docs. (`#2365 `__) + + +Contributors to this Release +---------------------------- + +Many thanks to those who contributed to this bugfix release: + +- `AkshayAwate `__ +- `CaselIT `__ +- `chitvs `__ +- `jap `__ +- `vytas7 `__ diff --git a/docs/changes/4.1.0.rst b/docs/changes/4.1.0.rst deleted file mode 100644 index c9a6f005a..000000000 --- a/docs/changes/4.1.0.rst +++ /dev/null @@ -1,25 +0,0 @@ -Changelog for Falcon 4.1.0 -========================== - -Summary -------- - -Falcon 4.1 is in development. The progress is tracked via the -`Version 4.1 milestone `__ -on GitHub. - - -Changes to Supported Platforms ------------------------------- - -.. NOTE(vytas): No changes to the supported platforms (yet). - - -.. towncrier release notes start - -Contributors to this Release ----------------------------- - -Many thanks to all of our talented and stylish contributors for this release! - -- `vytas7 `__ diff --git a/docs/changes/index.rst b/docs/changes/index.rst index cec3621d2..24deb80b7 100644 --- a/docs/changes/index.rst +++ b/docs/changes/index.rst @@ -3,7 +3,7 @@ Changelogs .. toctree:: - 4.1.0 <4.1.0> + 4.0.2 <4.0.2> 4.0.1 <4.0.1> 4.0.0 <4.0.0> 3.1.3 <3.1.3> diff --git a/docs/user/faq.rst b/docs/user/faq.rst index 6790c8773..414cf08d3 100644 --- a/docs/user/faq.rst +++ b/docs/user/faq.rst @@ -1348,47 +1348,44 @@ To include multiple values, simply use ``"; "`` to separate each name-value pair. For example, if you were to pass ``{'Cookie': 'xxx=yyy; hello=world'}``, you would get ``{'cookies': {'xxx': 'yyy', 'hello': 'world'}}``. -Why do I not see error tracebacks in ASGI applications? -------------------------------------------------------- +Why do I see no error tracebacks in my ASGI application? +-------------------------------------------------------- -When using Falcon with ASGI servers like Uvicorn, -you might notice that server errors do not display a traceback by default. -This behavior differs from WSGI applications, where errors are logged to `stderr`, -providing detailed tracebacks. +When using Falcon with an ASGI server like Uvicorn, +you might notice that server errors do not include any traceback by default. +This behavior differs from WSGI, where the PEP-3333 specification defines the +`wsgi.errors `__ stream +(which Falcon utilizes to log unhandled +:class:`internal server errors `). -The reason for this is that ASGI does not define a standardized way to log errors back to the application server, -unlike WSGI. Therefore, you need to configure logging manually to see these tracebacks. +Since there is no standardized way to log errors back to the ASGI server, +the framework simply opts to log them using the ``falcon`` +:class:`logger `. -Here’s how to set up logging in your ASGI Falcon application to capture error tracebacks: +The easiest way to get started is configuring the root logger via +:func:`logging.basicConfig`: .. code:: python import logging + import falcon import falcon.asgi logging.basicConfig( - format="%(asctime)s [%(levelname)s] %(message)s", - level=logging.INFO - ) + format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO) + - class ThingsResource: + class FaultyResource: async def on_get(self, req, resp): raise ValueError('foo') - app = falcon.asgi.App() - things = ThingsResource() - app.add_route('/things', things) - -By adding the above logging configuration, you will see tracebacks like this in your console: -.. code-block:: none + app = falcon.asgi.App() + app.add_route('/things', FaultyResource()) - [ERROR] [FALCON] Unhandled exception in ASGI app - Traceback (most recent call last): - File "<...>", line 12, in on_get - raise ValueError('foo') - ValueError: foo +By adding the above logging configuration, you should now see tracebacks logged +to :any:`stderr ` when accessing ``/things``. -For additional details on this topic, -please refer to :ref:`debugging-asgi-applications`. \ No newline at end of file +For additional details on this topic, +please refer to :ref:`debugging_asgi_applications`. diff --git a/docs/user/tutorial-asgi.rst b/docs/user/tutorial-asgi.rst index 8450a9efd..2d10c9378 100644 --- a/docs/user/tutorial-asgi.rst +++ b/docs/user/tutorial-asgi.rst @@ -115,6 +115,68 @@ Woohoo, it works!!! Well, sort of. Onwards to adding some real functionality! +.. _debugging_asgi_applications: + +Debugging ASGI Applications +--------------------------- + +While developing and testing a Falcon ASGI application along the lines of this +tutorial, you may encounter unexpected issues or behaviors, be it a copy-paste +mistake, an idea that didn't work out, or unusual input where validation falls +outside of the scope of this tutorial. + +Unlike WSGI, the ASGI specification has no standard mechanism for logging +errors back to the application server, so Falcon falls back to the stdlib's +:mod:`logging` (using the ``falcon`` :class:`logger `). + +As a well-behaved library, Falcon does not configure any loggers since that +might interfere with the user's logging setup. +Here's how you can set up basic logging in your ASGI Falcon application via +:func:`logging.basicConfig`: + +.. code:: python + + import logging + + import falcon + + logging.basicConfig(level=logging.INFO) + + + class ErrorResource: + def on_get(self, req, resp): + raise Exception('Something went wrong!') + + + app = falcon.App() + app.add_route('/error', ErrorResource()) + +When the above route is accessed, Falcon will catch the unhandled exception and +automatically log an error message. Below is an example of what the log output +might look like: + +.. code-block:: none + + ERROR:falcon.asgi.app:Unhandled exception in ASGI application + Traceback (most recent call last): + File "/path/to/your/app.py", line 123, in __call__ + resp = resource.on_get(req, resp) + File "/path/to/your/app.py", line 7, in on_get + raise Exception("Something went wrong!") + Exception: Something went wrong! + +.. note:: + While logging is helpful for development and debugging, be mindful of + logging sensitive information. Ensure that log files are stored securely + and are not accessible to unauthorized users. + +.. note:: + Unhandled errors are only logged automatically by Falcon's default error + handler for :class:`Exception`. If you + :meth:`replace this handler ` with your + own generic :class:`Exception` handler, you are responsible for logging or + reporting these errors yourself. + .. _asgi_tutorial_config: Configuration @@ -963,54 +1025,6 @@ adding ``--cov-fail-under=100`` (or any other percent threshold) to our tests in multiple environments would most probably involve running ``coverage`` directly, and combining results. -.. _debugging-asgi-applications: - -Debugging ASGI Applications ---------------------------- -(This section also applies to WSGI applications) - -While developing and testing ASGI applications, understanding how to configure -and utilize logging can be helpful, especially when you encounter unexpected -issues or behaviors. - -By default, Falcon does not set up logging for you, -but Python's built-in :mod:`logging` module provides a flexible framework for -emitting and capturing log messages. Here's how you can set up basic logging in -your ASGI Falcon application: - -.. code:: python - - import logging - import falcon - - logging.basicConfig(level=logging.INFO) - - class ErrorResource: - def on_get(self, req, resp): - raise Exception('Something went wrong!') - - app = falcon.App() - app.add_route('/error', ErrorResource()) - -When the above route is accessed, Falcon will catch the unhandled exception and -automatically log an error message. Below is an example of what the log output -might look like: - -.. code-block:: none - - ERROR:falcon.asgi.app:Unhandled exception in ASGI application - Traceback (most recent call last): - File "path/to/falcon/app.py", line 123, in __call__ - resp = resource.on_get(req, resp) - File "/path/to/your/app.py", line 7, in on_get - raise Exception("Something went wrong!") - Exception: Something went wrong! - -.. note:: - While logging is helpful for development and debugging, be mindful of logging - sensitive information. Ensure that log files are stored securely and are not - accessible to unauthorized users. - What Now? --------- diff --git a/falcon/typing.py b/falcon/typing.py index dd4aa8212..b1532442a 100644 --- a/falcon/typing.py +++ b/falcon/typing.py @@ -21,19 +21,28 @@ from falcon.asgi import SSEvent Headers = Dict[str, str] -"""Headers dictionary returned by the framework.""" +"""Headers dictionary returned by the framework. + +.. versionadded:: 4.0 +""" # WSGI class ReadableIO(Protocol): - """File-like protocol that defines only a read method.""" + """File-like protocol that defines only a read method. + + .. versionadded:: 4.0 + """ def read(self, n: Optional[int] = ..., /) -> bytes: ... # ASGI class AsyncReadableIO(Protocol): - """Async file-like protocol that defines only a read method, and is iterable.""" + """Async file-like protocol that defines only a read method, and is iterable. + + .. versionadded:: 4.0 + """ async def read(self, n: Optional[int] = ..., /) -> bytes: ... def __aiter__(self) -> AsyncIterator[bytes]: ... @@ -42,4 +51,6 @@ def __aiter__(self) -> AsyncIterator[bytes]: ... SSEEmitter = AsyncIterator[Optional['SSEvent']] """Async generator or iterator over Server-Sent Events (instances of :class:`falcon.asgi.SSEvent`). + +.. versionadded:: 4.0 """ diff --git a/falcon/version.py b/falcon/version.py index 21a355c2c..8698e5afa 100644 --- a/falcon/version.py +++ b/falcon/version.py @@ -14,5 +14,5 @@ """Falcon version.""" -__version__ = '4.1.0.dev1' +__version__ = '4.0.2' """Current version of Falcon.""" diff --git a/pyproject.toml b/pyproject.toml index ac71c10c4..21001d2f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ include = ["falcon*"] [tool.towncrier] package = "falcon" package_dir = "" - filename = "docs/changes/4.1.0.rst" + filename = "docs/changes/4.0.2.rst" directory = "docs/_newsfragments" issue_format = "`#{issue} `__"