-
-
Notifications
You must be signed in to change notification settings - Fork 387
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: Enum OAS generation (#3518) #3525
fix: Enum OAS generation (#3518) #3525
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3525 +/- ##
==========================================
+ Coverage 98.35% 98.36% +0.01%
==========================================
Files 344 344
Lines 15485 15489 +4
Branches 1707 1708 +1
==========================================
+ Hits 15230 15236 +6
+ Misses 122 121 -1
+ Partials 133 132 -1 ☔ View full report in Codecov by Sentry. |
Thanks for the PR, but I believe your work kind of invalidates the work done here #2550? Can you check the MCVE from the issue and confirm if this affects anything? @JacobCoffee and @peterschutt ping for awareness |
I feel like there's been some lengthy discussion around this recently, in discord I think?? Is this that same issue? |
This is the only thread I am aware of, raised by the same person |
The one I'm thinking of starts here - it was a long discussion and not put into a thread unfortunately. Feels related. |
Hi, @Alc-Alc and @peterschutt, thx for your comments. I read the #2546 issue and i think it is related to my issue, but it is more about the work of enum with optional in general
I fix just two test - test_optional_enum and part with enum params in test_create_parameters. So other things with optional fields work as before CC: @provinzkraut |
Have you confirmed if your changes are not affecting that? from enum import StrEnum
from litestar import Litestar, get
from litestar.params import Parameter
class Environment(StrEnum):
DEVELOPMENT = "dev"
TEST = "test"
QA = "qa"
@get(path="/")
async def test(
environment: Environment | None
= Parameter(
title="Environment",
description="The environment to filter by.",
default=None,
required=False,
),
) -> Environment | None:
return environment
app = Litestar([test]) Current main Your branch |
Yes, my changes affected that, because Enum now putted in components And it is really strange, that swagger register Enum object here I think we should ask the swagger team about this) |
@Alc-Alc Just a ping:) |
apologies for the delay here and thanks for being responsive, I am not much of an authority when it comes to OpenAPI, but if you claim it's a swagger issue, then I have nothing against this being merged, however I would appreciate if @peterschutt and other @litestar-org/maintainers take a look. |
@peterschutt @Alc-Alc |
Not from me at this point @wallseat - apologies, but I've got a bit going on at work at the moment and haven't had a chance to look at anything litestar related for the last few weeks. |
@provinzkraut |
@wallseat The fix seem reasonable, I'm just concerned about breaking swagger. I'd like to confirm this is an issue on their end, not ours (and potentially just avoid breaking this for our users, since a lot of them use this as their primary interactive docs 👀) IMO we can move forward if we have either confirmation that it's a swagger bug, or a way to not break this for our users. |
Hello everyone! I have the same problem with What will happen with this fix? |
@wallseat @Alc-Alc I observed that different Swagger UI results are displayed depending on whether Case 1 - When "parameters": [
{
"name": "environment",
"in": "query",
"schema": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/Environment"
}
],
"title": "Environment",
"description": "The environment to filter by."
},
"description": "The environment to filter by.",
"required": false,
"deprecated": false,
"allowEmptyValue": false,
"allowReserved": false
}
] Case 2 - When "parameters": [
{
"name": "environment",
"in": "query",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
],
"title": "Environment",
"description": "The environment to filter by."
},
"description": "The environment to filter by.",
"required": false,
"deprecated": false,
"allowEmptyValue": false,
"allowReserved": false
}
] @wallseat How about changing it to generate the ref schema first? Additionally, in the case of FastAPI, it generates the ref schema first when handling the same enum | None parameter. {
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/": {
"get": {
"summary": "Test",
"operationId": "test__get",
"parameters": [
{
"name": "environment",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
],
"title": "Environment",
"description": "The environment to filter by.",
"required": false
},
"description": "The environment to filter by."
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
],
"title": "Response Test Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Environment": {
"type": "string",
"enum": [
"dev",
"test",
"qa"
],
"title": "Environment"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
} |
@findstar This is very interesting! Since I can't imagine the order making a difference being a deliberate design choice, maybe this actually is a bug in swagger and an issue should be filed there? |
@provinzkraut @findstar Hi. I’ve recently encountered this issue as well, and I’d like to share my opinion. Additionally, I’m happy to provide up to date patch of this PR that can be applied to the main branch, especially since this PR has been open for quite a while. I believe this PR attempts to address 1 out of 2 issues:
Issue 2 is currently circumvented in this PR by using the oneOf keyword instead of the type keyword. (I’m not sure if it should be anyOf instead, because based on the FastAPI-generated output, it uses anyOf rather than oneOf.) As @findstar mentioned, swagger-ui seems to have a bug where the order of types matters ("string" and "null" works, but "null" and "string" doesn’t). However, according to the OpenAPI spec (OAI/OpenAPI-Specification#2244 (comment)), it’s clear that the order of "string" and "null" shouldn’t matter. So, filing an issue with swagger-ui seems reasonable. Or litestar can maybe change the code to change the generated order if it takes a long time to fix in swagger-ui. That said, I think problems 1 and 2 still persist in the current version of litestar and should be addressed on litestar’s side. Problem 1 is particularly important because, when using a code generator to create client types from OpenAPI specs, you end up with many duplicated enum types on the client side, even though there’s only one Enum type on the server side. FastAPI fixed this years ago (fastapi/fastapi#1303). |
Hi @isyangban, @findstar I changed an order, in which elements are generated, however, there are no changes in the swagger Test code example: from enum import Enum
from typing import Optional
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from litestar.params import Parameter
class Environment(Enum):
DEVELOPMENT = "dev"
TEST = "test"
QA = "qa"
@get(path="/")
async def test(
environment: Optional[Environment] = Parameter( # noqa: B008
title="Environment",
description="The environment to filter by.",
default=None,
required=False,
),
) -> Optional[Environment]:
return environment
app = Litestar(
[test],
openapi_config=OpenAPIConfig(
title="test",
version="0.0.1",
path="/docs",
root_schema_site="swagger",
),
) Generated OAS: {
"info": {
"title": "test",
"version": "0.0.1"
},
"openapi": "3.1.0",
"servers": [
{
"url": "/"
}
],
"paths": {
"/": {
"get": {
"summary": "Test",
"operationId": "Test",
"parameters": [
{
"name": "environment",
"in": "query",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
],
"title": "Environment",
"description": "The environment to filter by."
},
"description": "The environment to filter by.",
"required": false,
"deprecated": false,
"allowEmptyValue": false,
"allowReserved": false
}
],
"responses": {
"200": {
"description": "Request fulfilled, document follows",
"headers": {
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
]
}
}
}
},
"400": {
"description": "Bad request syntax or unsupported method",
"content": {
"application/json": {
"schema": {
"properties": {
"status_code": {
"type": "integer"
},
"detail": {
"type": "string"
},
"extra": {
"additionalProperties": {
},
"type": [
"null",
"object",
"array"
]
}
},
"type": "object",
"required": [
"detail",
"status_code"
],
"description": "Validation Exception",
"examples": [
{
"status_code": 400,
"detail": "Bad Request",
"extra": {
}
}
]
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"Environment": {
"type": "string",
"enum": [
"dev",
"test",
"qa"
],
"title": "Environment",
"description": "An enumeration."
}
}
}
} |
Should we update default pinned version? |
I tested the following openapi.json with Swagger UI version 5.18.2 {
"info": {
"title": "Test API",
"version": "0.0.1"
},
"openapi": "3.1.0",
"servers": [
{
"url": "/"
}
],
"paths": {
"/": {
"get": {
"summary": "Test",
"operationId": "Test",
"parameters": [
{
"name": "environment",
"in": "query",
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
],
"title": "Environment",
"description": "The environment to filter by."
},
"description": "The environment to filter by.",
"required": false,
"deprecated": false,
"allowEmptyValue": false,
"allowReserved": false
}
],
"responses": {
"200": {
"description": "Request fulfilled, document follows",
"headers": {
},
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/Environment"
},
{
"type": "null"
}
]
}
}
}
},
"400": {
"description": "Bad request syntax or unsupported method",
"content": {
"application/json": {
"schema": {
"properties": {
"status_code": {
"type": "integer"
},
"detail": {
"type": "string"
},
"extra": {
"additionalProperties": {
},
"type": [
"null",
"object",
"array"
]
}
},
"type": "object",
"required": [
"detail",
"status_code"
],
"description": "Validation Exception",
"examples": [
{
"status_code": 400,
"detail": "Bad Request",
"extra": {
}
}
]
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"Environment": {
"type": "string",
"enum": [
"dev",
"test",
"qa"
],
"title": "Environment"
}
}
}
} Upon checking more precisely, I found that versions up to 5.17.9 display the same results as before, but starting from version 5.17.10, a revised view is displayed. OAS issue swagger-api/swagger-ui#7912 and PR swagger-api/swagger-ui#9934 cc. isyangban |
Bumping swagger to the non-broken version seems to address all the open issues here. With that done, this PR would be good to merge I think. @Alc-Alc for a second opinion :) |
@isyangban Maybe we can keep using oneOf? because according to the RFC that's how it is https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-10.2.1.2 |
* return one_of
@wallseat In my view, the most compatible approach would be to use type: [T, "null"] when T is a primitive type, and oneOf: [{type: T}, {type: "null"}] otherwise. However, for simplicity, we could just stick with oneOf: [{type: T}, {type: "null"}] for now. These are just only my personal thoughts, and I think it’s best for the litestar maintainers @provinzkraut and @Alc-Alc to decide?. |
@isyangban |
I think this PR is ready to review and merge.
Can you pls restart runners? They failed on pdm setup cc: @isyangban @findstar |
I am fixing that in this PR :) |
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 ran this locally and I can see the swagger page I expect.
Thanks @wallseat for keeping up with this PR. My bad for any delays I have caused 🙂
Also, thanks to @findstar and @isyangban for reporting your findings 🔥
@provinzkraut this is a good to go from me
Documentation preview will be available shortly at https://litestar-org.github.io/litestar-docs-preview/3525 |
@wallseat can you address the coverage issues by adding the missing test cases? |
@provinzkraut Done |
…ill always be executed
I added a "no branch" pragma because the method in question is always executed with enums (in the context of its current usage). It did not seem like a benefit to add a new test to check a behavior that would not happen in the actual workflow. The doc errors are unrelated to this PR. Will update if I find anything wrt that. |
@litestar-org/maintainers can we merge this considering it failed due to doc issues? |
Changes described here #3518
Closes
Closes #3518