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 routing middleware #1497

Merged
merged 4 commits into from
Apr 19, 2022
Merged

Add routing middleware #1497

merged 4 commits into from
Apr 19, 2022

Conversation

RobbeSneyders
Copy link
Member

Part of #1489

This PR adds a routing middleware, which adds the connexion operation to the scope passed to the next middlewares.

The middleware is implemented with a starlette router on which the api endpoints are registered. For all other routes, the router acts ass a pass-through.

@RobbeSneyders RobbeSneyders added this to the Connexion 3.0 milestone Mar 17, 2022
@RobbeSneyders RobbeSneyders changed the base branch from main to feature/swagger-ui-middleware March 17, 2022 23:05
@RobbeSneyders RobbeSneyders changed the title Feature/routing middleware Add routing middleware Mar 17, 2022
@Ruwann
Copy link
Member

Ruwann commented Mar 18, 2022

Some more context for possible issues of subclassing starlette.BaseHTTPMiddleware: encode/starlette#1029

Also this comment and the corresponding PR: encode/starlette#1519 (comment)

@RobbeSneyders
Copy link
Member Author

Thanks for the additional context @Ruwann, I was indeed already running into some of these issues.
I was able to refactor and simplify the approach of the routing middleware, and I removed our own BaseHTTPMiddleware.

Note that there's some quick fixes in this PR that I still want to tackle later. Right now, I'm trying to extract all functionality into middleware with minimal effort via maximal reuse of existing connexion code (like the Api and Operation classes), and maximal usage of Starlette tools.

@RobbeSneyders RobbeSneyders force-pushed the feature/swagger-ui-middleware branch 3 times, most recently from f67a8dd to bbeb817 Compare March 31, 2022 19:45
Base automatically changed from feature/swagger-ui-middleware to main April 10, 2022 15:15
Copy link
Member Author

@RobbeSneyders RobbeSneyders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is ready for review @Ruwann

@@ -27,7 +27,6 @@ def test_errors(problem_app):
error405 = json.loads(get_greeting.data.decode('utf-8', 'replace'))
assert error405['type'] == 'about:blank'
assert error405['title'] == 'Method Not Allowed'
assert error405['detail'] == 'The method is not allowed for the requested URL.'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Flask specific and not set by Starlette.

@@ -13,15 +13,3 @@ def fake_json_auth(token, required_scopes=None):
return json.loads(token)
except ValueError:
return None


async def async_basic_auth(username, password, required_scopes=None, request=None):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some cleanup, these functions are not actually used in the tests.

@coveralls
Copy link

coveralls commented Apr 11, 2022

Pull Request Test Coverage Report for Build 2163779258

  • 147 of 154 (95.45%) changed or added relevant lines in 11 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.008%) to 93.292%

Changes Missing Coverage Covered Lines Changed/Added Lines %
connexion/apis/abstract.py 45 46 97.83%
connexion/exceptions.py 3 4 75.0%
connexion/middleware/exceptions.py 12 14 85.71%
connexion/middleware/routing.py 70 73 95.89%
Totals Coverage Status
Change from base Build 2157288191: -0.008%
Covered Lines: 2712
Relevant Lines: 2907

💛 - Coveralls

Comment on lines 124 to 135
:param specification: OpenAPI specification. Can be provided either as dict, or as path
to file.
:param base_path: Base path to host the API.
:param arguments: Jinja arguments to resolve in specification.
:param resolver: Callable that maps operationID to a function
:param resolver_error_handler: Callable that generates an Operation used for handling
ResolveErrors
:param debug: Flag to run in debug mode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument docstrings are a bit unintuitive to me: (a subset of) the parameters of the superclass are described here and some of this class' arguments are described.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, wanted to describe parameters of both this class and superclass, but seems like I forgot to update some. Will clean it up.

self.app = self.create_app()
self.middleware = self._apply_middleware()

middlewares = middlewares or ConnexionMiddleware.default_middlewares
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't immediately think of a concrete example/usecase, but if the app should be created without any middlewares, this is now not really possible (unless overriding ConnexionMiddleware.default_middlewares). Should this be allowed by e.g. making a distinction between None and []?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really see a use case for it, but the current code can indeed at least lead to some unexpected behavior. I'll put it as default for the argument or handle the [] explicitly.

api_base_path = scope.get('root_path', '')[len(original_scope.get('root_path', '')):]

if CONNEXION_CONTEXT not in original_scope:
original_scope[CONNEXION_CONTEXT] = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the convention is, but should we put it under extensions in the scope instead of directly at the top level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know about this. Makes sense.

if CONNEXION_CONTEXT not in original_scope:
original_scope[CONNEXION_CONTEXT] = {}

original_scope[CONNEXION_CONTEXT].update({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, the dict.setdefault() simplifies this a bit (although perhaps a bit less untuitive as it's less known):

original_scope.setdefault(CONNEXION_CONTEXT, {}).update({'api_base_path': api_base_path})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of the one-liner, but can indeed use setdefault for the first part.

Factor out starlette BaseHTTPMiddleware

Fix exceptions for starlette < 0.19

Fix docstring formatting

Rename middleware/base.py to abstract.py

Rework routing middleware
@RobbeSneyders
Copy link
Member Author

Is this one good to go @Ruwann?

response = app_client.post("/v1.0/greeting/robbe")

assert response.headers.get('operation_id') == 'fakeapi.hello.post_greeting', \
response.status_code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the additional response.status_code here? Should this be a separate assert?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just raises the status code as info if the test fails.

@RobbeSneyders RobbeSneyders merged commit 84e33e5 into main Apr 19, 2022
@RobbeSneyders RobbeSneyders deleted the feature/routing-middleware branch April 19, 2022 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants