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

feat: Add raise_on_unexpected_status flag to Client #593

Merged
merged 9 commits into from
Dec 18, 2022

Conversation

JamesHinshelwood
Copy link
Contributor

@JamesHinshelwood JamesHinshelwood commented Mar 12, 2022

Progress toward #491

Not sure this is the right approach. Just wanted to raise a PR to gather feedback :)

@JamesHinshelwood JamesHinshelwood force-pushed the raise-on-error-code branch 6 times, most recently from b16fc5c to a31befa Compare March 12, 2022 13:01
@codecov
Copy link

codecov bot commented Mar 26, 2022

Codecov Report

Merging #593 (a3bd7c8) into main (99638b1) will not change coverage.
The diff coverage is 100.00%.

@@            Coverage Diff            @@
##              main      #593   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           49        49           
  Lines         1966      1969    +3     
=========================================
+ Hits          1966      1969    +3     
Impacted Files Coverage Δ
openapi_python_client/__init__.py 100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@dbanty
Copy link
Collaborator

dbanty commented Mar 26, 2022

Hey @JamesHinshelwood, thanks for the contribution! I am wondering if maybe we should just expose the underlying httpx.Response object within our own Response class that comes back from the _detailed functions. Then folks could more easily do whatever they want with it after the fact (e.g., calling the raise_for_status method). It also would let them choose per-request which responses should generate exceptions instead of doing it at the client level which is more explicit and therefore (I think) better?

This could come in the form of subclassing (if it doesn't get too messy to mix in with generics) or as an inner attribute to which we delegate some methods for easier access.

What do you think?

@JamesHinshelwood
Copy link
Contributor Author

Hey @JamesHinshelwood, thanks for the contribution! I am wondering if maybe we should just expose the underlying httpx.Response object within our own Response class that comes back from the _detailed functions. Then folks could more easily do whatever they want with it after the fact (e.g., calling the raise_for_status method). It also would let them choose per-request which responses should generate exceptions instead of doing it at the client level which is more explicit and therefore (I think) better?

This could come in the form of subclassing (if it doesn't get too messy to mix in with generics) or as an inner attribute to which we delegate some methods for easier access.

What do you think?

For my use-case, what I really want is an API where I can just call an endpoint and handle the 'expected' responses, then I would get an exception raised for anything unexpected. Even exposing the response directly wouldn't be ideal for me, because then I still need to convert the 'unexpected' response to exceptions myself.

I've realised though that 'expected' doesn't necessarily mean 200 <= x < 300. I think the better behaviour would be to throw if the returned status code was something not listed in the openapi specification. Looking at the generated code, I think this would effectively mean changing the else branch in _parse_response:

def _parse_response(*, response: httpx.Response) -> Optional[GoodResponse]:
    if response.status_code == 200:
        response_200 = GoodResponse.from_dict(response.json())

        return response_200
    # return None
    raise Exception(f"Unexpected status code: {response.status_code})

Also tagging @theFong @ramnes @johnthagen @JorgeGarciaIrazabal who expressed an interest in the discussion, but might have missed this PR.

@JamesHinshelwood JamesHinshelwood changed the title feat: Add raise_on_error_code flag to Client feat: Add raise_on_unexpected_status flag to Client Apr 14, 2022
@JamesHinshelwood
Copy link
Contributor Author

I've updated this PR with what I (hopefully) described above. Let me know what you think.

@ramnes
Copy link
Contributor

ramnes commented Apr 19, 2022

Raising on 2xx responses doesn't feel right to me, why do you feel it's needed? A lot of APIs use 201 and 204, for example.

@JamesHinshelwood
Copy link
Contributor Author

Raising on 2xx responses doesn't feel right to me, why do you feel it's needed? A lot of APIs use 201 and 204, for example.

If the openapi.yaml specification doesn't list a status code in the responses list, then I consider it 'unexpected' and thus raise an exception. Therefore, if the specification only lists 200 as the expected response code, then yes this PR will raise an exception for other 2xx statuses.

@ramnes
Copy link
Contributor

ramnes commented Apr 21, 2022

Ah, right, sorry! I thought you were always raising on non-200 status codes. LGTM. :)

@dbanty
Copy link
Collaborator

dbanty commented Jun 3, 2022

This is definitely an interesting concept—it basically makes the client stricter by enforcing that the server only returns the types that the spec says it will. I cannot count the amount of times I have seen undocumented responses from servers 😓. I think this is a good behavior to have, I would just like a couple of tweaks to make it a bit more user friendly:

  1. Add a note in the Client docstring about what that parameter does. We should probably document all the parameters at some point but this is a good place to start! I've been using Google style docstrings in this project I think, so it would be under an Attributes: section.
  2. Declare a new exception type and raise that rather than a base exception so it's easier to users to catch it specifically if they want to. This should probably be defined in the shared types module and imported to everywhere it's needed.
  3. Document the potential of raising that exception (and mention the conditions under which it happens) using a Raises section in the docstrings of all the top-level functions which could raise this. I wish Python just had a way to represent exceptions with the type system but... as long as it doesn't, we should make sure to document in docstrings.

@gwenshap
Copy link

This seems really important, otherwise you need the full _detailed in order to check whether None means "expected 204" or "unexpected error".

From @dbanty response, it looks like we are only 3 small steps away from getting this in?
@JamesHinshelwood if you are too busy, I'll be happy to help and nudge it along.

@JamesHinshelwood
Copy link
Contributor Author

@gwenshap Thanks for the offer. It will be a while before I can look at this again. If you're willing to rebase and address @dbanty 's comments I'd be very grateful! I've given you access to my fork, but feel free to just take my commits and raise a new PR if preferred.

Copy link
Collaborator

@dbanty dbanty left a comment

Choose a reason for hiding this comment

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

I finished this one off, thanks for the work everyone!

@dbanty dbanty merged commit 77a01bf into openapi-generators:main Dec 18, 2022
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

5 participants