Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inspection module to replace print_routes #1661

Merged
merged 38 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
25a83f6
feat(inspec): add inspect module to support programmatic inspection o…
CaselIT Feb 1, 2020
082e318
refactor(print_routes): use the new inspect module in print_routes co…
CaselIT Feb 1, 2020
d41fb46
feat(inspect): add inspection of app, static routes and sinks
CaselIT Feb 1, 2020
d7181bc
refactor(print_routes): use inspect_app by default
CaselIT Feb 1, 2020
75920c1
feat(inspect): add support for error handlers inspection
CaselIT Feb 1, 2020
90574bd
feat(inspect): add support for middleware inspection
CaselIT Feb 2, 2020
2608a10
chore(inspect): cleanup code
CaselIT Feb 2, 2020
18a7697
refactor(inspect): cleanup code, add types
CaselIT Feb 2, 2020
23fbefa
style(inspect): fix quote style
CaselIT Feb 2, 2020
bf9242d
refactor(inspect): move inspect module out of util. Rename script
CaselIT Feb 2, 2020
403bd6c
feat(inspect): use a visitor to transform the info class to string
CaselIT Feb 3, 2020
30607ca
Merge branch 'master' into inspect-api
CaselIT Feb 24, 2020
eb13d02
test(inspect): add tests to inspect_app command line
CaselIT Feb 24, 2020
0aa87b9
chore: add license, fix pep
CaselIT Feb 25, 2020
455b857
test: fix import error by adding __init__ in test module while testing
CaselIT Feb 25, 2020
182c285
test: add initial inspect tests
CaselIT Feb 25, 2020
53f8dc3
test: fix broken tests
CaselIT Feb 26, 2020
0461afb
test: more tests
CaselIT Feb 26, 2020
860ae7a
test: continue adding test to string visitor
CaselIT Feb 27, 2020
98929d5
test: complete tests of inspect module
CaselIT Feb 28, 2020
8c7b370
Merge branch 'master' into inspect-api
CaselIT Feb 28, 2020
6d630e3
chore: add missing test and restore previous cmd alias
CaselIT Feb 28, 2020
b32e501
docs(inspect): add inspect documentation
CaselIT Feb 29, 2020
1bbc4f4
chore: ignore routes without a responder. Improve middleware str output
CaselIT Feb 29, 2020
c67319c
chore: use the falcon-inspect-app in the doc, improve module resoluti…
CaselIT Feb 29, 2020
be0198c
Merge branch 'master' into inspect-api
kgriffs Mar 3, 2020
7dc8f97
Merge branch 'master' into inspect-api
kgriffs Mar 4, 2020
2519300
chore: deprecate print routes command
CaselIT Mar 4, 2020
61cd320
refactor: split inspect and verbose
CaselIT Mar 4, 2020
e69f92d
chore: add missing test
CaselIT Mar 4, 2020
2d7a272
docs: document internal flag
CaselIT Mar 4, 2020
17b939a
Merge branch 'master' into inspect-api
kgriffs Mar 17, 2020
23bad19
Merge branch 'master' into inspect-api
kgriffs Mar 22, 2020
bd0a74d
fix(mypy): fix mypy errors
CaselIT Mar 22, 2020
051d05b
doc(inspect.rst): Minor edits for organization and clarity
kgriffs Mar 27, 2020
ab61a58
doc(inspect): Tweak docstrings
kgriffs Mar 28, 2020
8bba95d
doc(inspect.rst): Fix typo
kgriffs Mar 28, 2020
02d7829
doc(inspect): "The" ==> "A"
kgriffs Mar 28, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[run]
branch = True
source = falcon
omit = falcon/tests*,falcon/cmd*,falcon/bench*,falcon/vendor/*
omit = falcon/tests*,falcon/cmd/bench.py,falcon/bench*,falcon/vendor/*

parallel = True

Expand Down
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ matrix:
env: TOXENV=check_vendored

- name: Python 3.8 (Windows)
env: TOXENV=py38_nocover
env:
- TOXENV=py38_nocover
- PYTHONIOENCODING=utf8
os: windows
language: bash
before_install:
Expand Down
3 changes: 3 additions & 0 deletions docs/_newsfragments/1435.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added inspect module to collect information about an application regarding
the registered routes, middlewares, static routes, sinks and error handlers
(See also: :ref:`inspect`.)
1 change: 1 addition & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ Framework Reference
cors
hooks
routing
inspect
util
testing
159 changes: 159 additions & 0 deletions docs/api/inspect.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
.. _inspect:

Inspect Module
==============

This module allows inspecting a Falcon application to obtain information
regarding the registered routes, middlewares, static routes, sinks and
error handlers using the corresponding functions of the module.
The entire application can be inspected by using the :func:`.inspect_app`

The ``falcon-inspect-app`` script uses the inspect module to print a
string representation of an application, like in the example below:

.. code:: bash

# my_module is the module that defined tha application under the name app
falcon-inspect-app my_module:app

The output would be:

.. code::

Falcon App (WSGI)
• Routes:
⇒ /foo - MyResponder:
├── DELETE - on_delete
├── GET - on_get
└── POST - on_post
⇒ /foo/{id} - MyResponder:
├── DELETE - on_delete_id
├── GET - on_get_id
└── POST - on_post_id
⇒ /bar - OtherResponder:
├── DELETE - on_delete_id
├── GET - on_get_id
└── POST - on_post_id
• Middleware (Middleware are independent):
→ MyMiddleware.process_request
→ OtherMiddleware.process_request

↣ MyMiddleware.process_resource
↣ OtherMiddleware.process_resource

├── Process route responder

↢ OtherMiddleware.process_response
↢ CORSMiddleware.process_response
• Static routes:
↦ /tests/ /path/to/tests [/path/to/test/index.html]
↦ /falcon/ /path/to/falcon
• Sinks:
⇥ /sink_cls SinkClass
⇥ /sink_fn sinkFn
• Error handlers:
⇜ RuntimeError my_runtime_handler

The example above returns the default output of the :meth:`.AppInfo.to_string`
method. A more verbose version can be obtained by passing ``verbose=True`` to it.
``falcon-inspect-app`` has a ``--verbose`` flag to enable this mode.

The output above can also be obtained by programatically using the inspect module.
This is a python script that returns the same output as the ``falcon-inspect-app``
command:

.. code:: python

from falcon import inspect
from my_module import app

app_info = inspect.inspect_app(app)
print(app_info)

The values returned by the inspect functions are class instances that
contain the relevant information collected from the application, to
facilitate programatically use of the collected data.

To support inspection of applications that use a custom router, the
module supplies :func:`.register_router` that registers
an handler function for a particular router class.
The default :class:`.CompiledRouter` inspection is
handled by the :func:`.inspect_compiled_router`
function.

The returned information classes can be explored using a visitor
pattern. To create the string representation of the classes the
:class:`.StringVisitor` visitor is used.
This class is instantiated automatically when calling ``str()``
on an instance or then using the ``to_string()`` method.
Custom visitor can subclass :class:`.InspectVisitor` and
use the :meth:`.InspectVisitor.process` method to visit
the classes.

Inspect module content
----------------------

Inspect Functions
~~~~~~~~~~~~~~~~~

The inspect module defines the following inspect functions

.. autofunction:: falcon.inspect.inspect_app

.. autofunction:: falcon.inspect.inspect_routes

.. autofunction:: falcon.inspect.inspect_middlewares

.. autofunction:: falcon.inspect.inspect_static_routes

.. autofunction:: falcon.inspect.inspect_sinks

.. autofunction:: falcon.inspect.inspect_error_handlers

Router Inspection Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Functions used to register custom inspect function for custom router implementation,
and the default inspector for the :class:`.CompiledRouter`

.. autofunction:: falcon.inspect.register_router

.. autofunction:: falcon.inspect.inspect_compiled_router

Inspect Information Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Data returned by the the inspect functions

.. autoclass:: falcon.inspect.AppInfo
:members:

.. autoclass:: falcon.inspect.RouteInfo

.. autoclass:: falcon.inspect.RouteMethodInfo

.. autoclass:: falcon.inspect.MiddlewareInfo

.. autoclass:: falcon.inspect.MiddlewareTreeInfo

.. autoclass:: falcon.inspect.MiddlewareClassInfo

.. autoclass:: falcon.inspect.MiddlewareTreeItemInfo

.. autoclass:: falcon.inspect.MiddlewareMethodInfo

.. autoclass:: falcon.inspect.StaticRouteInfo

.. autoclass:: falcon.inspect.SinkInfo

.. autoclass:: falcon.inspect.ErrorHandlerInfo

Visitors
~~~~~~~~

Classes used to traverse the information classes

.. autoclass:: falcon.inspect.InspectVisitor
:members:

.. autoclass:: falcon.inspect.StringVisitor
28 changes: 28 additions & 0 deletions docs/user/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,31 @@ To test this example go to the another terminal and run:
.. code:: bash

$ http localhost:8000/1/things authorization:custom-token

To visualize the application configuration the :ref:`inspect` can be used:

.. code:: bash

falcon-inspect-app things_advanced:app

This would print for this example application:

.. code::

Falcon App (WSGI)
• Routes:
⇒ /{user_id}/things - ThingsResource:
├── GET - on_get
└── POST - on_post
• Middleware (Middleware are independent):
→ AuthMiddleware.process_request
→ RequireJSON.process_request
→ JSONTranslator.process_request

├── Process route responder

↢ JSONTranslator.process_response
• Sinks:
⇥ /search/(?P<engine>ddg|y)\Z SinkAdapter
• Error handlers:
⇜ StorageError handle
31 changes: 31 additions & 0 deletions docs/user/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,22 @@ representation of the "images" resource.
threaded web server, resources and their dependencies must be
thread-safe.

We can use the the :ref:`inspect` to visualize the application configuration:

.. code:: bash

falcon-inspect-app look.app:app

This prints the following, correctly indicating that we are handling ``GET``
requests in the ``/images`` route:

.. code::

Falcon App (WSGI)
• Routes:
⇒ /images - Resource:
└── GET - on_get

So far we have only implemented a responder for GET. Let's see what
happens when a different method is requested:

Expand Down Expand Up @@ -1253,6 +1269,21 @@ HTTPie won't display the image, but you can see that the
response headers were set correctly. Just for fun, go ahead and paste
the above URI into your browser. The image should display correctly.

Inspecting the application now returns:

.. code:: bash

falcon-inspect-app look.app:get_app

.. code::

Falcon App (WSGI)
• Routes:
⇒ /images - Collection:
├── GET - on_get
└── POST - on_post
⇒ /images/{name} - Item:
└── GET - on_get

.. Query Strings
.. -------------
Expand Down
91 changes: 91 additions & 0 deletions falcon/cmd/inspect_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python
# Copyright 2013 by Rackspace Hosting, Inc.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Script that prints out the routes of an App instance.
"""
import argparse
import importlib
import os
import sys

import falcon
from falcon.inspect import inspect_app, inspect_routes, StringVisitor

sys.path.append(os.getcwd())


def make_parser():
"""Creates the parsed or the application"""
parser = argparse.ArgumentParser(
description='Example: falcon-inspect-app myprogram:app'
)
parser.add_argument(
'-r',
'--route_only',
action='store_true',
help='Prints only the information regarding the routes',
)
parser.add_argument(
'-v', '--verbose', action='store_true', help='More verbose output',
)
parser.add_argument(
'app_module',
help='The module and app to inspect. Example: myapp.somemodule:api',
)
return parser


def load_app(parser, args):

try:
module, instance = args.app_module.split(':', 1)
except ValueError:
parser.error('The app_module must include a colon between the module and instance')
try:
app = getattr(importlib.import_module(module), instance)
except AttributeError:
parser.error('{!r} not found in module {!r}'.format(instance, module))

if not isinstance(app, falcon.App):
if callable(app):
app = app()
if not isinstance(app, falcon.App):
parser.error('{} did not return a falcon.App instance'.format(args.app_module))
else:
parser.error(
'The instance must be of falcon.App or be '
'a callable without args that returns falcon.App'
)
return app


def main():
"""
Main entrypoint.
"""
parser = make_parser()
args = parser.parse_args()
app = load_app(parser, args)
if args.route_only:
routes = inspect_routes(app)
visitor = StringVisitor(args.verbose)
for route in routes:
print(visitor.process(route))
else:
print(inspect_app(app).to_string(args.verbose))


if __name__ == '__main__': # pragma: no cover
main()
Loading