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

Support openapi specification for Tuple fields #3210

Closed
3 tasks done
victorbenichoux opened this issue Sep 14, 2021 · 3 comments
Closed
3 tasks done

Support openapi specification for Tuple fields #3210

victorbenichoux opened this issue Sep 14, 2021 · 3 comments
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@victorbenichoux
Copy link

Checks

  • I added a descriptive title to this issue
  • I have searched (google, github) for similar issues and couldn't find anything
  • I have read and followed the docs and still think this is a bug

Bug

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

pydantic version: 1.8.2
pydantic compiled: True
python version: 3.7.11 (default, Jul 13 2021, 09:42:02)  [Clang 12.0.0 (clang-1200.0.32.29)]
platform: Darwin-19.6.0-x86_64-i386-64bit
optional deps. installed: ['typing-extensions']

When creating openapi schemas, BaseModel fields that are Tuple are indistinguishable from List instances. This is despite the fact that the openapi specification now supports fixed-length lists (i.e. tuple-like).

Specifically, the following here is true:

import pydantic
from typing import List, Tuple

class ListModel(pydantic.BaseModel):
    items: List[str]

class TupleModel(pydantic.BaseModel):
    items: Tuple[str]
    
list_model_schema = ListModel.schema()
tuple_model_schema = TupleModel.schema()

assert list_model_schema["properties"] == tuple_model_schema["properties"]

This leads to an issue with the openapi specification generated by fastapi, in which the swagger can not correctly render the content of Tuple members, as referenced in these issues tiangolo/fastapi#3665 and tiangolo/fastapi#3782, and this pull request tiangolo/fastapi#3874

Suggestion

I think that it would make sense to support the openAPI specification for Tuple types, as a result, the specification of the
TupleModel would change from

{
  "title": "TupleModel",
  "type": "object",
  "properties": {
    "items": {
      "title": "Items",
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["items"]
}

to

{
  "title": "TupleModel",
  "type": "object",
  "properties": {
    "items": {
      "title": "Items",
      "type": "array",
      "prefixItems": [{ "type": "string" }],
      "items": false
    }
  },
  "required": ["items"]
}

Where, each field of the Tuple is specified by an entry in prefixItems (see the docs). In itself, this would allow extra arguments to the list, thus the fixed length nature of the Tuple is reflected by items being set to false (see here).

I have created a branch (https://github.com/victorbenichoux/pydantic/tree/fix-tuple) to implement this specification.

@victorbenichoux
Copy link
Author

Something worth noting is that the prefixItems syntax for tuples is not available in the openapi spec <3.1.0, as a result this will break dependencies that only support <=3.0. This is the case of swagger for example (used, e.g. in fastapi). We may want to refrain from doing this until swagger supports it.

@DvdGiessen
Copy link

Also just ran into this.

field_schema() transforms my_tuple: Tuple[float, float] into {"title": "My Tuple", "type": "array", "items": [{"type": "number"}, {"type": "number"}]}, which is not a correct OpenAPI schema.

Suggestion:

I think, without having to use anything specific to OpenAPI 3.1+, we can at least make it an valid schema by making use of oneOf and setting a min/max. This isn't perfect, but at least the OpenAPI specification that is generated would be a valid specification, so its output doesn't break other things.

The following change to https://github.com/samuelcolvin/pydantic/blob/4be3f45e20e6f3652572896fee062f44e1ff4ae5/pydantic/schema.py#L508 does this:

         else:
-            f_schema = {'type': 'array', 'items': sub_schema}
+            f_schema = {'type': 'array', 'items': { 'oneOf': sub_schema }, 'minItems': len(sub_schema), 'maxItems': len(sub_schema) }
         if field.shape == SHAPE_GENERIC:

Then we get {"title": "My Tuple", "type": "array", "items": {"oneOf": [{"type": "number"}, {"type": "number"}]}, "minItems": 2, "maxItems": 2}, which at least is an correct OpenAPI specification.

@Kludex
Copy link
Member

Kludex commented Apr 25, 2023

This should be solved in V2.

@Kludex Kludex closed this as not planned Won't fix, can't repro, duplicate, stale Apr 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X
Projects
None yet
Development

No branches or pull requests

3 participants