From 8dd9ef406fa833dd3d496699d56f0eb3434c0c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 5 Aug 2026 10:33:54 +0300 Subject: [PATCH] fix: generate top-level array schemas typed lists --- .../fix-array-schema-deserialization.md | 5 ++++ fingerprint_pro_server_api_sdk/api_client.py | 6 +++++ .../models/geolocation_subdivisions.py | 7 +++--- template/api_client.mustache | 6 +++++ template/model.mustache | 8 +++++- test/test_base_model.py | 25 +++++++++++++++++++ test/test_fingerprint_api.py | 13 +++++++++- 7 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 .changeset/fix-array-schema-deserialization.md diff --git a/.changeset/fix-array-schema-deserialization.md b/.changeset/fix-array-schema-deserialization.md new file mode 100644 index 00000000..57c12066 --- /dev/null +++ b/.changeset/fix-array-schema-deserialization.md @@ -0,0 +1,5 @@ +--- +"fingerprint-pro-server-api-python-sdk": patch +--- + +**events**: Fix parsing of `GeolocationSubdivisions` so `subdivisions` returns a typed list of `GeolocationSubdivision` diff --git a/fingerprint_pro_server_api_sdk/api_client.py b/fingerprint_pro_server_api_sdk/api_client.py index 2939efd7..a576ddff 100644 --- a/fingerprint_pro_server_api_sdk/api_client.py +++ b/fingerprint_pro_server_api_sdk/api_client.py @@ -590,6 +590,12 @@ def __deserialize_model(data, klass): if not klass.swagger_types and not ApiClientDeserializer.__hasattr(klass, 'get_real_child_model'): if hasattr(klass, '__parent_class__') and klass.__parent_class__ == 'dict': return klass(**data) + if hasattr(klass, '__parent_class__') and klass.__parent_class__ == 'list': + item_type = getattr(klass, '__list_item_type__', None) + if item_type is not None and isinstance(data, list): + return klass(ApiClientDeserializer.deserialize(sub_data, item_type) + for sub_data in data) + return klass(data) return data kwargs = {} diff --git a/fingerprint_pro_server_api_sdk/models/geolocation_subdivisions.py b/fingerprint_pro_server_api_sdk/models/geolocation_subdivisions.py index 569f9467..57ea2b00 100644 --- a/fingerprint_pro_server_api_sdk/models/geolocation_subdivisions.py +++ b/fingerprint_pro_server_api_sdk/models/geolocation_subdivisions.py @@ -15,7 +15,7 @@ from fingerprint_pro_server_api_sdk.base_model import BaseModel -class GeolocationSubdivisions(BaseModel): +class GeolocationSubdivisions(list): """NOTE: This class is auto generated by the swagger code generator program. Do not edit the class manually. @@ -36,7 +36,6 @@ class GeolocationSubdivisions(BaseModel): attribute_map = { } - def __init__(self): # noqa: E501 - """GeolocationSubdivisions - a model defined in Swagger""" # noqa: E501 - self.discriminator = None + __parent_class__ = 'list' + __list_item_type__ = 'GeolocationSubdivision' diff --git a/template/api_client.mustache b/template/api_client.mustache index 54f3abbe..4d33d9ce 100644 --- a/template/api_client.mustache +++ b/template/api_client.mustache @@ -582,6 +582,12 @@ class ApiClientDeserializer: if not klass.swagger_types and not ApiClientDeserializer.__hasattr(klass, 'get_real_child_model'): if hasattr(klass, '__parent_class__') and klass.__parent_class__ == 'dict': return klass(**data) + if hasattr(klass, '__parent_class__') and klass.__parent_class__ == 'list': + item_type = getattr(klass, '__list_item_type__', None) + if item_type is not None and isinstance(data, list): + return klass(ApiClientDeserializer.deserialize(sub_data, item_type) + for sub_data in data) + return klass(data) return data kwargs = {} diff --git a/template/model.mustache b/template/model.mustache index 6ad97ffd..20d0f23c 100644 --- a/template/model.mustache +++ b/template/model.mustache @@ -23,7 +23,7 @@ from typing_extensions import deprecated {{#schema.deprecated}} @deprecated("This class is deprecated. Please avoid using it in new code.") {{/schema.deprecated}} -class {{classname}}({{#parent}}{{parent}}{{/parent}}{{^parent}}BaseModel{{/parent}}): +class {{classname}}({{#isArrayModel}}list{{/isArrayModel}}{{^isArrayModel}}{{#parent}}{{parent}}{{/parent}}{{^parent}}BaseModel{{/parent}}{{/isArrayModel}}): """{{#description}} {{{.}}} @@ -72,6 +72,11 @@ class {{classname}}({{#parent}}{{parent}}{{/parent}}{{^parent}}BaseModel{{/paren } {{/discriminator}} +{{#isArrayModel}} + __parent_class__ = 'list' + __list_item_type__ = '{{{arrayModelType}}}' +{{/isArrayModel}} +{{^isArrayModel}} {{#parent}} __parent_class__ = '{{parent}}' {{/parent}} @@ -85,6 +90,7 @@ class {{classname}}({{#parent}}{{parent}}{{/parent}}{{^parent}}BaseModel{{/paren {{/vars}} self.discriminator = {{#discriminator}}'{{discriminator}}'{{/discriminator}}{{^discriminator}}None{{/discriminator}} {{/parent}} +{{/isArrayModel}} {{#vars}}{{#@first}} {{/@first}} {{#required}} diff --git a/test/test_base_model.py b/test/test_base_model.py index e6991853..c6e389e5 100644 --- a/test/test_base_model.py +++ b/test/test_base_model.py @@ -91,6 +91,31 @@ def test_to_dict_with_dict_of_models(self): } self.assertEqual(model.to_dict(), expected) + def test_to_dict_with_list_of_models(self): + """Test conversion to dictionary when a list attribute holds models. + + This mirrors how array schemas (e.g. GeolocationSubdivisions) expose a + list of typed items that each must be serialized via to_dict(). + """ + model = ExampleModel( + name="Test Model", + details={"key": "value"}, + items=[SubModel(id=1, value="first"), SubModel(id=2, value="second")], + sub_model=self.sub_model, + ) + expected = { + 'name': "Test Model", + 'details': {"key": "value"}, + 'items': [{'id': 1, 'value': 'first'}, {'id': 2, 'value': 'second'}], + 'sub_model': {'id': 1, 'value': 'sub_value'}, + } + self.assertEqual(model.to_dict(), expected) + + def test_to_dict_with_list_of_mixed_items(self): + """Test that a list mixing models and primitives serializes each correctly.""" + model = ExampleModel(items=[SubModel(id=1, value="first"), "plain", 3]) + self.assertEqual(model.to_dict(), {'items': [{'id': 1, 'value': 'first'}, "plain", 3]}) + def test_to_str(self): """Test conversion to string.""" expected_str = pprint.pformat(self.model.to_dict()) diff --git a/test/test_fingerprint_api.py b/test/test_fingerprint_api.py index a3e35e28..95c92928 100644 --- a/test/test_fingerprint_api.py +++ b/test/test_fingerprint_api.py @@ -18,7 +18,8 @@ from fingerprint_pro_server_api_sdk import (Configuration, ErrorResponse, ErrorPlainResponse, ErrorCode, RawDeviceAttributes, EventsUpdateRequest, RelatedVisitorsResponse, - SearchEventsResponse, SearchEventsResponseEvents, Products) + SearchEventsResponse, SearchEventsResponseEvents, Products, + GeolocationSubdivisions, GeolocationSubdivision) from fingerprint_pro_server_api_sdk.api.fingerprint_api import FingerprintApi # noqa: E501 from fingerprint_pro_server_api_sdk.rest import KnownApiException, ApiException from urllib.parse import urlencode @@ -274,6 +275,16 @@ def test_get_event_correct_data(self): self.assertIsNone(event_response_dict["products"]["identification"]["data"]["last_seen_at"]["subscription"]) self.assertIsInstance(event_response.products.raw_device_attributes.data, RawDeviceAttributes) + subdivisions = event_response.products.identification.data.ip_location.subdivisions + self.assertIsInstance(subdivisions, GeolocationSubdivisions) + self.assertEqual(len(subdivisions), 1) + self.assertIsInstance(subdivisions[0], GeolocationSubdivision) + self.assertEqual(subdivisions[0].iso_code, "63") + self.assertEqual(subdivisions[0].name, "North Rhine-Westphalia") + self.assertEqual( + event_response_dict["products"]["identification"]["data"]["ip_location"]["subdivisions"], + [{"iso_code": "63", "name": "North Rhine-Westphalia"}]) + def test_get_event_errors_200(self): """Test checks correct code run result in scenario of arrors in BotD or identification API""" mock_pool = MockPoolManager(self)