Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ Contributors (chronological)
- Felix Claessen `@Flix6x <https://github.com/Flix6x>`_
- Karthik Ramadugu `@karthiksai109 <https://github.com/karthiksai109>`_
- Amir Kahriman `@kingdomOfIT <https://github.com/kingdomOfIT>`_
- Sai Asish Y `@SAY-5 <https://github.com/SAY-5>`_
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changelog
unreleased
**********

Features:

- Support the marshmallow ``Tuple`` field (:issue:`399`).

Other changes:

- Drop support for marshmallow 3, which is EOL.
Expand Down
23 changes: 23 additions & 0 deletions src/apispec/ext/marshmallow/field_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
marshmallow.fields.Field: (None, None),
marshmallow.fields.Raw: (None, None),
marshmallow.fields.List: ("array", None),
marshmallow.fields.Tuple: ("array", None),
marshmallow.fields.IP: ("string", "ip"),
marshmallow.fields.IPv4: ("string", "ipv4"),
marshmallow.fields.IPv6: ("string", "ipv6"),
Expand Down Expand Up @@ -69,6 +70,7 @@
"enum",
"type",
"items",
"prefixItems",
"allOf",
"oneOf",
"anyOf",
Expand Down Expand Up @@ -112,6 +114,7 @@ def init_attribute_functions(self):
self.nested2properties,
self.pluck2properties,
self.list2properties,
self.tuple2properties,
self.dict2properties,
self.timedelta2properties,
self.datetime2properties,
Expand Down Expand Up @@ -510,6 +513,26 @@ def list2properties(self, field, **kwargs: typing.Any) -> dict:
ret["items"] = self.field2property(field.inner)
return ret

def tuple2properties(self, field, **kwargs: typing.Any) -> dict:
"""Return a dictionary of properties from :class:`Tuple <marshmallow.fields.Tuple>` fields.

A tuple is a fixed-length, ordered array. For OpenAPI 3.1 the contained
fields are described with ``prefixItems``; for earlier versions they are
described with ``items`` using ``oneOf``.

:param Field field: A marshmallow field.
:rtype: dict
"""
ret: dict = {}
if isinstance(field, marshmallow.fields.Tuple):
tuple_fields = [self.field2property(f) for f in field.tuple_fields]
ret["minItems"] = ret["maxItems"] = len(tuple_fields)
if self.openapi_version.major >= 3 and self.openapi_version.minor >= 1:
ret["prefixItems"] = tuple_fields
else:
ret["items"] = {"oneOf": tuple_fields}
return ret

def dict2properties(self, field, **kwargs: typing.Any) -> dict:
"""Return a dictionary of properties from :class:`Dict <marshmallow.fields.Dict>` fields.

Expand Down
31 changes: 31 additions & 0 deletions tests/test_ext_marshmallow_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,3 +715,34 @@ def test_field2property_examples(spec_fixture):
assert res == {
"examples": ["foo", "bar"],
}


def test_tuple_field_translates_to_array(spec_fixture):
field = fields.Tuple((fields.String, fields.Integer))
res = spec_fixture.openapi.field2property(field)
assert res["type"] == "array"
assert res["minItems"] == res["maxItems"] == 2


@pytest.mark.parametrize("spec_fixture", ("3.1.0",), indirect=True)
def test_tuple_field_uses_prefix_items_on_openapi_31(spec_fixture):
field = fields.Tuple((fields.String, fields.Integer))
res = spec_fixture.openapi.field2property(field)
assert res["prefixItems"] == [
spec_fixture.openapi.field2property(fields.String()),
spec_fixture.openapi.field2property(fields.Integer()),
]
assert "items" not in res


@pytest.mark.parametrize("spec_fixture", ("2.0", "3.0.0"), indirect=True)
def test_tuple_field_uses_items_one_of_before_openapi_31(spec_fixture):
field = fields.Tuple((fields.String, fields.Integer))
res = spec_fixture.openapi.field2property(field)
assert res["items"] == {
"oneOf": [
spec_fixture.openapi.field2property(fields.String()),
spec_fixture.openapi.field2property(fields.Integer()),
]
}
assert "prefixItems" not in res