From 27b46d0fb60f9944a33dcee5575c20562e5eff9b Mon Sep 17 00:00:00 2001 From: "T. Franzel" Date: Sat, 4 Mar 2023 17:49:52 +0100 Subject: [PATCH] honor djangorestframework_camel_case settings "ignore_keys" and "ignore_fields" #945 Thx to @tomashchuk for raising the issue and providing part of the fix Co-authored-by: tomashchuk --- .../contrib/djangorestframework_camel_case.py | 22 ++++++--- .../test_djangorestframework_camel_case.py | 14 ++++++ .../test_djangorestframework_camel_case.yml | 45 +++++++++++++++++++ tox.ini | 3 ++ 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/drf_spectacular/contrib/djangorestframework_camel_case.py b/drf_spectacular/contrib/djangorestframework_camel_case.py index 34eeec14..57a70ed1 100644 --- a/drf_spectacular/contrib/djangorestframework_camel_case.py +++ b/drf_spectacular/contrib/djangorestframework_camel_case.py @@ -1,17 +1,29 @@ import re +from typing import Optional def camelize_serializer_fields(result, generator, request, public): + from djangorestframework_camel_case.settings import api_settings from djangorestframework_camel_case.util import camelize_re, underscore_to_camel - def camelize_str(str): - return re.sub(camelize_re, underscore_to_camel, str) + # prunes subtrees from camelization based on owning field name + ignore_fields = api_settings.JSON_UNDERSCOREIZE.get("ignore_fields") or () + # ignore certain field names while camelizing + ignore_keys = api_settings.JSON_UNDERSCOREIZE.get("ignore_keys") or () - def camelize_component(schema: dict): - if schema.get('type') == 'object': + def camelize_str(key: str) -> str: + new_key = re.sub(camelize_re, underscore_to_camel, key) if "_" in key else key + if key in ignore_keys or new_key in ignore_keys: + return key + return new_key + + def camelize_component(schema: dict, name: Optional[str] = None) -> dict: + if name is not None and (name in ignore_fields or camelize_str(name) in ignore_fields): + return schema + elif schema.get('type') == 'object': if 'properties' in schema: schema['properties'] = { - camelize_str(field_name): camelize_component(field_schema) + camelize_str(field_name): camelize_component(field_schema, field_name) for field_name, field_schema in schema['properties'].items() } if 'required' in schema: diff --git a/tests/contrib/test_djangorestframework_camel_case.py b/tests/contrib/test_djangorestframework_camel_case.py index 38be8c29..ab8f4844 100644 --- a/tests/contrib/test_djangorestframework_camel_case.py +++ b/tests/contrib/test_djangorestframework_camel_case.py @@ -28,17 +28,23 @@ class NestedObject(TypedDict): field_four: str field_five: NestedObject2 field_six: List[NestedObject2] + field_ignored: int class FakeSerializer(serializers.Serializer): field_one = serializers.CharField() field_two = serializers.CharField() + field_ignored = serializers.CharField() field_nested = serializers.SerializerMethodField() + field_nested_ignored = serializers.SerializerMethodField() def get_field_nested(self) -> NestedObject: # type: ignore pass # pragma: no cover + def get_field_nested_ignored(self) -> NestedObject: # type: ignore + pass # pragma: no cover + class FakeViewset(mixins.ListModelMixin, viewsets.GenericViewSet): serializer_class = FakeSerializer @@ -53,6 +59,14 @@ def home(self, request): 'drf_spectacular.settings.spectacular_settings.POSTPROCESSING_HOOKS', [camelize_serializer_fields] ) +@mock.patch( + 'djangorestframework_camel_case.settings.api_settings.JSON_UNDERSCOREIZE', + { + 'no_underscore_before_number': False, + 'ignore_fields': ('field_nested_ignored',), + 'ignore_keys': ('field_ignored',), + } +) @pytest.mark.contrib('djangorestframework_camel_case') def test_camelize_serializer_fields(): assert_schema( diff --git a/tests/contrib/test_djangorestframework_camel_case.yml b/tests/contrib/test_djangorestframework_camel_case.yml index 33c68041..4b6f9c51 100644 --- a/tests/contrib/test_djangorestframework_camel_case.yml +++ b/tests/contrib/test_djangorestframework_camel_case.yml @@ -46,6 +46,8 @@ components: type: string fieldTwo: type: string + field_ignored: + type: string fieldNested: type: object properties: @@ -75,14 +77,57 @@ components: required: - fieldEight - fieldSeven + field_ignored: + type: integer required: - fieldFive - fieldFour + - field_ignored - fieldSix - fieldThree readOnly: true + fieldNestedIgnored: + type: object + properties: + field_three: + type: integer + field_four: + type: string + field_five: + type: object + properties: + field_seven: + type: integer + field_eight: + type: string + required: + - field_eight + - field_seven + field_six: + type: array + items: + type: object + properties: + field_seven: + type: integer + field_eight: + type: string + required: + - field_eight + - field_seven + field_ignored: + type: integer + required: + - field_five + - field_four + - field_ignored + - field_six + - field_three + readOnly: true required: + - field_ignored - fieldNested + - fieldNestedIgnored - fieldOne - fieldTwo securitySchemes: diff --git a/tox.ini b/tox.ini index 22762d99..464cece1 100644 --- a/tox.ini +++ b/tox.ini @@ -152,3 +152,6 @@ ignore_missing_imports = True [mypy-rest_framework_gis.*] ignore_missing_imports = True + +[mypy-djangorestframework_camel_case.*] +ignore_missing_imports = True \ No newline at end of file