-
-
Notifications
You must be signed in to change notification settings - Fork 772
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
[aiohttp] Multipart handling #987
Conversation
I stumbled into one issue. Will try to dig into that. I might need to compare flask implementation to make it reliable the same. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job. Had some comments, the one starting with (nit) address some potential issues rather than request for changes.
connexion/apis/aiohttp_api.py
Outdated
# body has been read or not | ||
# body_exists refers to underlying stream of data | ||
'has_body': req.body_exists, | ||
'can_ready_body': req.can_read_body, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo here -> can_read_body
connexion/apis/aiohttp_api.py
Outdated
logger.debug('Reading multipart data from request') | ||
data = yield from req.post() | ||
|
||
files = {k: v for k, v in data.items() if isinstance(v, web.FileField)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Having one loop will save some milliseconds for some high-load connexion service
if isinstance(v, web.FileField) and k not in files: | ||
files[k] = v | ||
elif isinstance(v, web.FileField) and k in files: | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any concerns of using collections.defaultdict
instead of handling an exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH I have doubts if that is correct. It works for many files but if I mix normal fields and files everything goes wrong.
Also form is processed in weird way, I will post details łatwe for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting thing. I noticed that if you try to submit multiple fields for Flask it will ignore them and take just the first value.
I might want to create an issue on its own for that :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: #992
Ok, along the way I noticed that file params in aiohttp at least are not propely taken care of if pythonParams is in place. Eh..I will create an issue later. |
@@ -216,8 +216,9 @@ def redirect(request): | |||
@aiohttp_jinja2.template('index.j2') | |||
@asyncio.coroutine | |||
def _get_swagger_ui_home(self, req): | |||
return {'openapi_spec_url': (self.base_path + | |||
self.options.openapi_spec_path)} | |||
return {'openapi_spec_url': ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated but flake8
was unhappy about it.
rebased that on top the master to include fixes for |
changes: - different handling for multipart and non-multipart requests - tests confirming the behavior - minor list to typle changes for collection that ought to be immutable Fixes spec-first#975
@hjacobs sorry for being so annoying 😺 but we kinda need this. I think I 2 different PRs that my PR might require to work properly (they are enlisted in top comment). Would you mind checking them out first. I actually left some reviews there but 2nd pair of eye would be most welcomed nad helpful. |
@prawn-cake do you have any idea if form items (those that are not files) shoudl reach handler exploded or wrapped in |
@kornicameister not sure if I got what you mean. |
I meant the case where you have multipart/request and how it suppose to be passed to handler? |
Okay, got it. Good question, I have no answer. In general, it's a bit hard to read code-specific comments without code context. Took me awhile to wrap my head around |
Yeah, I know...I suppose the pros can be that if you're dealing with either json request or form request you expect consistent behavior in a handler no matter what is sent. It is always called a |
About
And going back to the original question
If |
[Swagger 3, Python 3, Flask] Just had a similar issue between Oddly i was able to work around this by putting the Connexion returned the |
@kornicameister, too bad. I may continue looking at it. Did you find a suitable alternative? Did you abandon connexion or use something other than its aiohttp driver? Thanks |
@ddurham2 I try to play around |
@prawn-cake: I'd like to see connexion handle things with aiohttp as well as it does with the flask api. However, in some dark corners, there are some things in abstract.py that seem to assume flask's datatypes (e.g. operations.abstract.py:_get_file_arguments() assumes a werkzeug.datatypes.MultiDict in that it calls getlist, but aiohttp passed along a normal dict). Hence, is the goal with aiohttp to make aiohttp_api.py pass along flask types (making abstract.py happy) or to alter abstract.py to handle either flask or aiohttp types? (thereby making it rather not abstract) I'm inclined to convert things to flask types in aiohttp_api.py:get_request() when constructing ConnectionRequest().. but then these things get passed to handlers.. which, when writing for aiohttp connexion seems to ask the handler writer to expect aiohttp types and to return them as well. I need a direction. Thanks |
Well, I discovered that this "dark corner" was committed to master just a few days ago (#1000). But my general question still stands. |
@ddurham2 that's exactly my point. There's no solid contract that Flask/aiohttp or any other thing could rely on. |
What is the status on this PR? |
@@ -273,7 +273,7 @@ def _get_body_argument(self, body, arguments, has_kwargs, sanitize): | |||
|
|||
if x_body_name in arguments or has_kwargs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kornicameister, you and I are both trying to get aiohttp to work well with connexion. I can't wait until this PR is accepted.
Possibly another thing to add in this area of change:
Can you at all fathom why the 'or has_kwargs' is here??? I'm trying to create decorators for my handlers. The decorators generically pass all args along as **kwargs. However, when **kwargs is present in the handler, then this code kicks in and rather than returning parameters from the body broken out, it returns the body itself as a single arg. This makes much more difficult to create decorators.
There's a similar condition (but not problematic for me) also on line 263 above.
The original change doesn't describe why. It's just "connexion 2.0" in the blame.
I would propose removing the 'or has_kwargs' so that parameters are the same whether you break them out as named arguments in the handler or accept any named argument in the handler. Thoughts?
For now I've worked around it by expanding 'body' into the kwargs at the beginning of the decorator's wrapper, but I don't know if that handles all cases, and it just seems inconsistent of connexion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ddurham2 I have tried to untagle the code in different parts of connexion without too much luck. I am afraid I can't help you. Due to various issues, like this one, we're forced to move away from connexion
thus it is no longer my top priority to invest my time here.
Although, I am tracking what's going on, and my original opinion on state of affairs in connexion had not changed. Without strong API contract enforced & required by connexion
it will be hard to make connextion
, Flask
and aiohttp
implementation compliant.
And, since that contract is not in place, it is close to impossible or, at the very least, really hard to ensure proper implementation.
One example of such situation is handling this multipart, I believe I've already mentioned that, where files from request are assumed to be dist-ish
structure and as long as dict-ish
structure is returned from Flask
and aiohttp
there little thing that makes a difference and that's implementation of that dict-ish
being. TBH & AFAIR connexion
requires that being to just be iterable
or have get
method which is rather poor expectation (all hail strong typed languages where weird stuff like that has lower chance to occur :/ ).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it were up to me, I'd have ditched that PR and focus on work to prepare strong internal API contract toward making aiohttp
and Flask
plugins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you find an alternative to connexion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are alternatives but concept is a mirror reflection, essentially it is code that comes first.
I am spending my time playing around with strongly typed self-creation :) now, it is available in my repositories ( axion ). It's no place to talk about it, but if you decide to check it out, let me know. It's mess from place to place but maybe sometime later I will clean it all up.
- Taken mostly from existing PR: spec-first#987
…uplicate keys as the rest of connexion expects - Based parly on existing PR: spec-first#987
* Added unit tests to demonstrate the problems of #975 - Taken mostly from existing PR: #987 * now splitting out multipart POSTs into files[] and form[], handling duplicate keys as the rest of connexion expects - Based parly on existing PR: #987 * rewrote how operations/openapi.py::_get_body_argument() works to better build the arguments[] list according to what the spec says and what the handler accepts. This fixes a bug when requests contain mixed files and form values and the handler is expecting variable names matching the request property names. * Adding unit tests to improve code converage test * post merge fixes - using 'async' keyword now in new unit test file * unit test improvements -- now testing the contents of the files we upload too * making some code a bit clearer regarding duplicate names of file submissions * fixing up unit tests since merging main * fixing isort-check-tests and flake8 * clarified a comment * comment correction * after discussions with maintainer, reverted _get_body_argument back to the original where it does not attempt to break out the body into individual arguments for the handler. But left in changes that make the normal behavior of not passing a body argument to a handler without one more consistent when the body itself is empty or not an object type. * fixing unit tests after after reverting _get_body_argument behavior
* Added unit tests to demonstrate the problems of spec-first#975 - Taken mostly from existing PR: spec-first#987 * now splitting out multipart POSTs into files[] and form[], handling duplicate keys as the rest of connexion expects - Based parly on existing PR: spec-first#987 * rewrote how operations/openapi.py::_get_body_argument() works to better build the arguments[] list according to what the spec says and what the handler accepts. This fixes a bug when requests contain mixed files and form values and the handler is expecting variable names matching the request property names. * Adding unit tests to improve code converage test * post merge fixes - using 'async' keyword now in new unit test file * unit test improvements -- now testing the contents of the files we upload too * making some code a bit clearer regarding duplicate names of file submissions * fixing up unit tests since merging main * fixing isort-check-tests and flake8 * clarified a comment * comment correction * after discussions with maintainer, reverted _get_body_argument back to the original where it does not attempt to break out the body into individual arguments for the handler. But left in changes that make the normal behavior of not passing a body argument to a handler without one more consistent when the body itself is empty or not an object type. * fixing unit tests after after reverting _get_body_argument behavior
Changes proposed in this pull request::
Fixes #975
Supersedes #976
Potentially requires: