-
Notifications
You must be signed in to change notification settings - Fork 1
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
fix: support $ref from endpoint response to components/responses BNCH-109662 #213
Conversation
@@ -991,6 +991,20 @@ | |||
} | |||
} | |||
}, |
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.
All of the files under end_to_end_tests
can be ignored - they're just example data that's used by a test that runs the generator against a big spec file and checks for the expected output files.
if not ref_path.startswith("/components/responses/"): | ||
return ParseError(data=data, detail=f"$ref to {data.ref} not allowed in responses"), schemas | ||
resp_data = responses.get(ref_path.split("/")[-1], None) | ||
if not resp_data: | ||
return ParseError(data=data, detail=f"Could not find reference: {data.ref}"), schemas | ||
if not isinstance(resp_data, oai.Response): | ||
return ParseError(data=data, detail="Top-level $ref inside components/responses is not supported"), schemas |
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.
instead of individual return statements like these,... what if we used a error_detail: str
variable and just have one return statement at the end of this function that has one return
statement for ParseError
-- this may or may not look very pretty but at least it would avoid disabling PLR0911
rule
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.
I honestly find it easier to follow the logic when there's explicit short-circuiting like this. Maybe it's because I did a lot of Go and that's the preferred pattern in Go. But if I see a bunch of this kind of thing—
if bad_condition_1:
error_detail = message1
elif bad_condition_2:
error_detail = message2
else:
error_detail = None
if error_detail:
return ParseError(data=data, detail=error_detail)
—it bothers me a little because now the code paths for the error conditions are not really independent of each other; if for instance I messed up and put "if" instead of "elif" for the second one, now the bad_condition_1 test would be effectively a no-op. It also means that I have to be constructing the same kind of error, ParseError, each time (and the same way each time: data=data
), which happens to be true in this case but isn't logically inevitable; if I had to make a change where now one of those cases raises a different error, I'd have to refactor the whole thing.
PLR0911 is only a helpful rule when it's helpful, and this codebase disables it in quite a few parsing/validation functions like this.
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.
PLR0911 is only a helpful rule when it's helpful, and
agree
this codebase disables it in quite a few parsing/validation functions like this
okay this sounds good to me
return ref_path, schemas | ||
if not ref_path.startswith("/components/responses/"): | ||
return ParseError(data=data, detail=f"$ref to {data.ref} not allowed in responses"), schemas | ||
resp_data = responses.get(ref_path.split("/")[-1], None) |
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.
I'm a little surprised that getting reference data is not a little more standardized or at least with a helper.... is doing a split
and -1
the general pattern in the rest of the repo?
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's no use case in the rest of the repo for getting just the type name from a reference.
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.
Note that this isn't really "getting reference data". The helper is just converting the string "#/components/schemas/MyTypeName"
to the string "MyTypeName"
.
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.
Whoops, I was wrong, there are a couple other usages like this. So I've added a helper.
*, | ||
status_code: HTTPStatus, | ||
data: Union[oai.Response, oai.Reference], | ||
schemas: Schemas, | ||
responses: Dict[str, Union[oai.Response, oai.Reference]], |
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.
this is a breaking change to this function's signature....What's the general methodology to validate that this is safe (possibly other places that might call this function?)
I think we should have good confidence from passing tests but just calling it out in case there's anything additional we need to consider
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.
It's validated by the existing unit tests for the repo, which include 1. a bunch of tests of building an Endpoint
(which is literally the only code path response_from_data
is called from), and 2. a full end-to-end test of running the generator on an entire API spec that includes endpoints with responses.
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.
building an Endpoint (which is literally the only code path response_from_data is called from
oh okayy cool... this is what I wanted to confirm!
def get_reference_simple_name(ref_path: str) -> str: | ||
""" | ||
Takes a path like `/components/schemas/NameOfThing` and returns a string like `NameOfThing`. | ||
""" | ||
return ref_path.split("/", 3)[-1] | ||
|
||
|
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.
nice 👍
Fixes openapi-generators#1125.
I've also opened a PR from the same branch in the upstream repo (here), but here I'm targeting it to our fork repo in against a new
2.x
branch (which I created by pulling the latest release code onmain
from the upstream repo). Themain
branch in our fork repo is still the same as it was, that is, a heavily modified fork of a very old version of the upstream repo.See also: https://benchling.atlassian.net/wiki/spaces/AE/pages/730268159/Fixes+necessary+to+use+openapi-python-client+for+v3+API
Why we need this fix: The V3 API spec makes heavy use of reusable response definitions like this, so that we don't have to spell out the same standard error responses for every endpoint.
This makes it so the following—
—is exactly equivalent to:
Previously, it would have failed to follow the response reference and behaved as if the response had no content.
This still requires the response to be fully defined inline in
components/responses
; it can't itself consist of just a$ref
.