From 904b343ebc5a4049bbbfc58c4bbdf584cdc61ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timotheus=20R=C3=B6ck?= Date: Wed, 19 Oct 2022 14:11:13 +0200 Subject: [PATCH] Add support for marshmallow.fields.Number.as_string --- AUTHORS.rst | 1 + src/apispec/ext/marshmallow/field_converter.py | 18 ++++++++++++++++++ tests/test_ext_marshmallow_field.py | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index afe779fb..79a6bcef 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -76,3 +76,4 @@ Contributors (chronological) - ``_ - Edwin Erdmanis `@vorticity `_ - Mounier Florian `@paradoxxxzero `_ +- Timotheus Roeck `@rockTA ` diff --git a/src/apispec/ext/marshmallow/field_converter.py b/src/apispec/ext/marshmallow/field_converter.py index be987fe4..d9dab613 100644 --- a/src/apispec/ext/marshmallow/field_converter.py +++ b/src/apispec/ext/marshmallow/field_converter.py @@ -187,6 +187,7 @@ def field2type_and_format( # hierarchy until we find something that does. for field_class in type(field).__mro__: if field_class in self.field_mapping: + field_class = get_diverging_field_class_if_required(field, field_class) type_, fmt = self.field_mapping[field_class] break else: @@ -548,3 +549,20 @@ def make_min_max_attributes(validators, min_attr, max_attr) -> dict: if max_list: attributes[max_attr] = min(max_list) return attributes + + +def get_diverging_field_class_if_required( + field: marshmallow.fields.Field, field_class: typing.Type +) -> typing.Type: + """Return a field class that diverges from the origin class. + + This is currently only required for Number fields, because some applications serialize decimal + numbers as strings, as binary representation of floats can't be precise. However, if more fields + would allow diverging serializer fields in the future, this function could be extended. + """ + if ( + issubclass(field_class, marshmallow.fields.Number) + and getattr(field, "as_string", False) is True + ): + return marshmallow.fields.String + return field_class diff --git a/tests/test_ext_marshmallow_field.py b/tests/test_ext_marshmallow_field.py index 41143dd6..f42573cd 100644 --- a/tests/test_ext_marshmallow_field.py +++ b/tests/test_ext_marshmallow_field.py @@ -439,3 +439,12 @@ class _DesertSentinel: field.metadata[_DesertSentinel()] = "to be ignored" result = spec_fixture.openapi.field2property(field) assert result == {"description": "A description", "type": "boolean"} + + +def test_number_as_string_is_converted_as_expected(spec_fixture): + field = fields.Number( + as_string=True, + metadata={"description": "A number field that is serialized as a string"}, + ) + result = spec_fixture.openapi.field2property(field) + assert result["type"] == "string"