diff --git a/.changeset/accept-unknown-enum-values.md b/.changeset/accept-unknown-enum-values.md new file mode 100644 index 00000000..ee2f4c0b --- /dev/null +++ b/.changeset/accept-unknown-enum-values.md @@ -0,0 +1,5 @@ +--- +'@fingerprint/python-sdk': patch +--- + +Accept unknown enum values gracefully instead of throwing errors during deserialization diff --git a/fingerprint_server_sdk/models/bot_info.py b/fingerprint_server_sdk/models/bot_info.py index 5b706334..5b1e6163 100644 --- a/fingerprint_server_sdk/models/bot_info.py +++ b/fingerprint_server_sdk/models/bot_info.py @@ -17,7 +17,7 @@ import re # noqa: F401 from typing import Any, ClassVar, Optional -from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictStr from typing_extensions import Self @@ -45,22 +45,6 @@ class BotInfo(BaseModel): 'confidence', ] - @field_validator('identity') - def identity_validate_enum(cls, value: Any) -> Any: - """Validates the enum""" - if value not in set(['verified', 'signed', 'spoofed', 'unknown']): - raise ValueError( - "must be one of enum values ('verified', 'signed', 'spoofed', 'unknown')" - ) - return value - - @field_validator('confidence') - def confidence_validate_enum(cls, value: Any) -> Any: - """Validates the enum""" - if value not in set(['low', 'medium', 'high']): - raise ValueError("must be one of enum values ('low', 'medium', 'high')") - return value - model_config = ConfigDict( populate_by_name=True, validate_assignment=True, diff --git a/fingerprint_server_sdk/models/bot_info_category.py b/fingerprint_server_sdk/models/bot_info_category.py index 048bff29..b0cdb7a4 100644 --- a/fingerprint_server_sdk/models/bot_info_category.py +++ b/fingerprint_server_sdk/models/bot_info_category.py @@ -47,3 +47,15 @@ class BotInfoCategory(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of BotInfoCategory from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/bot_info_confidence.py b/fingerprint_server_sdk/models/bot_info_confidence.py index 84668e2e..89cb08af 100644 --- a/fingerprint_server_sdk/models/bot_info_confidence.py +++ b/fingerprint_server_sdk/models/bot_info_confidence.py @@ -34,3 +34,15 @@ class BotInfoConfidence(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of BotInfoConfidence from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/bot_info_identity.py b/fingerprint_server_sdk/models/bot_info_identity.py index 38f88951..2aeb8e4f 100644 --- a/fingerprint_server_sdk/models/bot_info_identity.py +++ b/fingerprint_server_sdk/models/bot_info_identity.py @@ -35,3 +35,15 @@ class BotInfoIdentity(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of BotInfoIdentity from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/bot_result.py b/fingerprint_server_sdk/models/bot_result.py index 50e5abc3..545a5b05 100644 --- a/fingerprint_server_sdk/models/bot_result.py +++ b/fingerprint_server_sdk/models/bot_result.py @@ -34,3 +34,15 @@ class BotResult(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of BotResult from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/error_code.py b/fingerprint_server_sdk/models/error_code.py index f39223c8..1655e282 100644 --- a/fingerprint_server_sdk/models/error_code.py +++ b/fingerprint_server_sdk/models/error_code.py @@ -49,3 +49,15 @@ class ErrorCode(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of ErrorCode from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/incremental_identification_status.py b/fingerprint_server_sdk/models/incremental_identification_status.py index 40a52686..397c035b 100644 --- a/fingerprint_server_sdk/models/incremental_identification_status.py +++ b/fingerprint_server_sdk/models/incremental_identification_status.py @@ -33,3 +33,15 @@ class IncrementalIdentificationStatus(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of IncrementalIdentificationStatus from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/proximity.py b/fingerprint_server_sdk/models/proximity.py index dcb6d980..14c8f17d 100644 --- a/fingerprint_server_sdk/models/proximity.py +++ b/fingerprint_server_sdk/models/proximity.py @@ -17,7 +17,7 @@ import re # noqa: F401 from typing import Annotated, Any, ClassVar, Optional, Union -from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr from typing_extensions import Self @@ -40,15 +40,6 @@ class Proximity(BaseModel): ) __properties: ClassVar[list[str]] = ['id', 'precision_radius', 'confidence'] - @field_validator('precision_radius') - def precision_radius_validate_enum(cls, value: Any) -> Any: - """Validates the enum""" - if value not in set([10, 25, 65, 175, 450, 1200, 3300, 8500, 22500]): - raise ValueError( - 'must be one of enum values (10, 25, 65, 175, 450, 1200, 3300, 8500, 22500)' - ) - return value - model_config = ConfigDict( populate_by_name=True, validate_assignment=True, diff --git a/fingerprint_server_sdk/models/proxy_confidence.py b/fingerprint_server_sdk/models/proxy_confidence.py index f0af2611..ae833aea 100644 --- a/fingerprint_server_sdk/models/proxy_confidence.py +++ b/fingerprint_server_sdk/models/proxy_confidence.py @@ -34,3 +34,15 @@ class ProxyConfidence(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of ProxyConfidence from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/proxy_details.py b/fingerprint_server_sdk/models/proxy_details.py index 2885ddf5..0c357f20 100644 --- a/fingerprint_server_sdk/models/proxy_details.py +++ b/fingerprint_server_sdk/models/proxy_details.py @@ -17,7 +17,7 @@ import re # noqa: F401 from typing import Any, ClassVar, Optional -from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr from typing_extensions import Self @@ -39,13 +39,6 @@ class ProxyDetails(BaseModel): ) __properties: ClassVar[list[str]] = ['proxy_type', 'last_seen_at', 'provider'] - @field_validator('proxy_type') - def proxy_type_validate_enum(cls, value: Any) -> Any: - """Validates the enum""" - if value not in set(['residential', 'data_center']): - raise ValueError("must be one of enum values ('residential', 'data_center')") - return value - model_config = ConfigDict( populate_by_name=True, validate_assignment=True, diff --git a/fingerprint_server_sdk/models/rare_device_percentile_bucket.py b/fingerprint_server_sdk/models/rare_device_percentile_bucket.py index 1f186a13..1c9120be 100644 --- a/fingerprint_server_sdk/models/rare_device_percentile_bucket.py +++ b/fingerprint_server_sdk/models/rare_device_percentile_bucket.py @@ -37,3 +37,15 @@ class RareDevicePercentileBucket(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of RareDevicePercentileBucket from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/rule_action_type.py b/fingerprint_server_sdk/models/rule_action_type.py index 3dc0f091..3e078f68 100644 --- a/fingerprint_server_sdk/models/rule_action_type.py +++ b/fingerprint_server_sdk/models/rule_action_type.py @@ -33,3 +33,15 @@ class RuleActionType(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of RuleActionType from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/sdk.py b/fingerprint_server_sdk/models/sdk.py index 48374082..ef06139b 100644 --- a/fingerprint_server_sdk/models/sdk.py +++ b/fingerprint_server_sdk/models/sdk.py @@ -17,7 +17,7 @@ import re # noqa: F401 from typing import Any, ClassVar, Optional -from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictStr from typing_extensions import Self from fingerprint_server_sdk.models.integration import Integration @@ -37,13 +37,6 @@ class SDK(BaseModel): integrations: Optional[list[Integration]] = None __properties: ClassVar[list[str]] = ['platform', 'version', 'integrations'] - @field_validator('platform') - def platform_validate_enum(cls, value: Any) -> Any: - """Validates the enum""" - if value not in set(['js', 'android', 'ios', 'unknown']): - raise ValueError("must be one of enum values ('js', 'android', 'ios', 'unknown')") - return value - model_config = ConfigDict( populate_by_name=True, validate_assignment=True, diff --git a/fingerprint_server_sdk/models/search_events_bot.py b/fingerprint_server_sdk/models/search_events_bot.py index 24ede8ea..1bcdfd8e 100644 --- a/fingerprint_server_sdk/models/search_events_bot.py +++ b/fingerprint_server_sdk/models/search_events_bot.py @@ -35,3 +35,15 @@ class SearchEventsBot(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of SearchEventsBot from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/search_events_bot_info.py b/fingerprint_server_sdk/models/search_events_bot_info.py index 97cddc4b..6e80098d 100644 --- a/fingerprint_server_sdk/models/search_events_bot_info.py +++ b/fingerprint_server_sdk/models/search_events_bot_info.py @@ -33,3 +33,15 @@ class SearchEventsBotInfo(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of SearchEventsBotInfo from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/search_events_incremental_identification_status.py b/fingerprint_server_sdk/models/search_events_incremental_identification_status.py index f58c5f7a..7469293d 100644 --- a/fingerprint_server_sdk/models/search_events_incremental_identification_status.py +++ b/fingerprint_server_sdk/models/search_events_incremental_identification_status.py @@ -33,3 +33,15 @@ class SearchEventsIncrementalIdentificationStatus(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of SearchEventsIncrementalIdentificationStatus from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/search_events_rare_device_percentile_bucket.py b/fingerprint_server_sdk/models/search_events_rare_device_percentile_bucket.py index 5984862e..de9cd812 100644 --- a/fingerprint_server_sdk/models/search_events_rare_device_percentile_bucket.py +++ b/fingerprint_server_sdk/models/search_events_rare_device_percentile_bucket.py @@ -37,3 +37,15 @@ class SearchEventsRareDevicePercentileBucket(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of SearchEventsRareDevicePercentileBucket from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/search_events_sdk_platform.py b/fingerprint_server_sdk/models/search_events_sdk_platform.py index 75e5c0fd..9d498aa3 100644 --- a/fingerprint_server_sdk/models/search_events_sdk_platform.py +++ b/fingerprint_server_sdk/models/search_events_sdk_platform.py @@ -34,3 +34,15 @@ class SearchEventsSdkPlatform(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of SearchEventsSdkPlatform from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/search_events_vpn_confidence.py b/fingerprint_server_sdk/models/search_events_vpn_confidence.py index 0837b41c..a0d15163 100644 --- a/fingerprint_server_sdk/models/search_events_vpn_confidence.py +++ b/fingerprint_server_sdk/models/search_events_vpn_confidence.py @@ -34,3 +34,15 @@ class SearchEventsVpnConfidence(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of SearchEventsVpnConfidence from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/tampering_confidence.py b/fingerprint_server_sdk/models/tampering_confidence.py index ab829a91..719073fe 100644 --- a/fingerprint_server_sdk/models/tampering_confidence.py +++ b/fingerprint_server_sdk/models/tampering_confidence.py @@ -34,3 +34,15 @@ class TamperingConfidence(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of TamperingConfidence from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/fingerprint_server_sdk/models/vpn_confidence.py b/fingerprint_server_sdk/models/vpn_confidence.py index a8a5bae9..c6fd5d48 100644 --- a/fingerprint_server_sdk/models/vpn_confidence.py +++ b/fingerprint_server_sdk/models/vpn_confidence.py @@ -34,3 +34,15 @@ class VpnConfidence(str, Enum): def from_json(cls, json_str: str) -> Self: """Create an instance of VpnConfidence from a JSON string""" return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, str): + raise ValueError(f'{value!r} is not a valid {cls.__name__}') + obj = str.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj diff --git a/template/model_enum.mustache b/template/model_enum.mustache new file mode 100644 index 00000000..72e9d381 --- /dev/null +++ b/template/model_enum.mustache @@ -0,0 +1,39 @@ +from __future__ import annotations +import json +from enum import Enum +{{#vendorExtensions.x-py-other-imports}} +{{{.}}} +{{/vendorExtensions.x-py-other-imports}} +from typing_extensions import Self + + +class {{classname}}({{vendorExtensions.x-py-enum-type}}, Enum): + """ + {{{description}}}{{^description}}{{{classname}}}{{/description}} + """ + + """ + allowed enum values + """ +{{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{value}}} + {{/enumVars}} + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of {{classname}} from a JSON string""" + return cls(json.loads(json_str)) + + @classmethod + def _missing_(cls, value: object) -> Self: + """Accept unknown enum values gracefully.""" + if not isinstance(value, {{vendorExtensions.x-py-enum-type}}): + raise ValueError(f"{value!r} is not a valid {cls.__name__}") + obj = {{vendorExtensions.x-py-enum-type}}.__new__(cls, value) + obj._name_ = str(value) + obj._value_ = value + # cache it so the same value won't trigger `_missing_` again + cls._value2member_map_[value] = obj + return obj +{{/allowableValues}} diff --git a/template/model_generic.mustache b/template/model_generic.mustache index 40a04841..e72fd3cf 100644 --- a/template/model_generic.mustache +++ b/template/model_generic.mustache @@ -59,41 +59,6 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} raise ValueError(r"must validate the regular expression {{{vendorExtensions.x-pattern}}}") return value {{/vendorExtensions.x-regex}} - {{#isEnum}} - - @field_validator('{{{name}}}') - def {{{name}}}_validate_enum(cls, value: Any) -> Any: - """Validates the enum""" - {{^required}} - if value is None: - return value - - {{/required}} - {{#required}} - {{#isNullable}} - if value is None: - return value - - {{/isNullable}} - {{/required}} - {{#isContainer}} - {{#isArray}} - for i in value: - if i not in set([{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]): - raise ValueError("each list item must be one of ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") - {{/isArray}} - {{#isMap}} - for i in value.values(): - if i not in set([{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]): - raise ValueError("dict values must be one of enum values ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") - {{/isMap}} - {{/isContainer}} - {{^isContainer}} - if value not in set([{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]): - raise ValueError("must be one of enum values ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") - {{/isContainer}} - return value - {{/isEnum}} {{/vars}} model_config = ConfigDict( diff --git a/test/test_unknown_enum_values.py b/test/test_unknown_enum_values.py new file mode 100644 index 00000000..2ff63a1c --- /dev/null +++ b/test/test_unknown_enum_values.py @@ -0,0 +1,145 @@ +import json +import unittest +from pathlib import Path +from typing import Any + +from fingerprint_server_sdk import Event +from fingerprint_server_sdk.models.bot_info import BotInfo +from fingerprint_server_sdk.models.proxy_details import ProxyDetails +from fingerprint_server_sdk.models.sdk import SDK + +MOCK_DIR = Path(__file__).resolve().parent / 'mocks' + + +class TestUnknownEnumValues(unittest.TestCase): + """Test that unknown/new enum values are accepted without errors.""" + + def _load_event_json(self) -> dict[str, Any]: + mock_file = MOCK_DIR / 'events' / 'get_event_200.json' + return json.loads(mock_file.read_text(encoding='utf-8')) + + def test_event_with_unknown_proxy_type(self) -> None: + """Unknown proxy_type value should be accepted and preserved.""" + data = self._load_event_json() + data['proxy_details']['proxy_type'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.proxy_details.proxy_type, 'unknown-value') + + def test_event_with_unknown_sdk_platform(self) -> None: + """Unknown SDK platform value should be accepted and preserved.""" + data = self._load_event_json() + data['sdk']['platform'] = 'new-platform' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.sdk.platform, 'new-platform') + + def test_event_with_unknown_bot_result(self) -> None: + """Unknown bot result value should be accepted and preserved.""" + data = self._load_event_json() + data['bot'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.bot, 'unknown-value') + + def test_event_with_unknown_vpn_confidence(self) -> None: + """Unknown vpn_confidence value should be accepted and preserved.""" + data = self._load_event_json() + data['vpn'] = True + data['vpn_confidence'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.vpn_confidence, 'unknown-value') + + def test_event_with_unknown_proxy_confidence(self) -> None: + """Unknown proxy_confidence value should be accepted and preserved.""" + data = self._load_event_json() + data['proxy_confidence'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.proxy_confidence, 'unknown-value') + + def test_event_with_unknown_tampering_confidence(self) -> None: + """Unknown tampering_confidence value should be accepted and preserved.""" + data = self._load_event_json() + data['tampering_confidence'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.tampering_confidence, 'unknown-value') + + def test_event_with_unknown_rare_device_percentile_bucket(self) -> None: + """Unknown rare_device_percentile_bucket value should be accepted and preserved.""" + data = self._load_event_json() + data['rare_device_percentile_bucket'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.rare_device_percentile_bucket, 'unknown-value') + + def test_proxy_details_with_unknown_proxy_type(self) -> None: + """ProxyDetails model should accept unknown proxy_type directly.""" + details = ProxyDetails.from_dict({'proxy_type': 'unknown-value', 'last_seen_at': 123}) + self.assertIsInstance(details, ProxyDetails) + self.assertEqual(details.proxy_type, 'unknown-value') + + def test_sdk_with_unknown_platform(self) -> None: + """SDK model should accept unknown platform directly.""" + sdk = SDK.from_dict({'platform': 'unknown-value', 'version': '1.0.0'}) + self.assertIsInstance(sdk, SDK) + self.assertEqual(sdk.platform, 'unknown-value') + + def test_bot_info_with_unknown_identity_and_confidence(self) -> None: + """BotInfo model should accept unknown identity and confidence values.""" + info = BotInfo.from_dict( + { + 'category': 'crawler', + 'provider': 'TestBot', + 'name': 'test', + 'identity': 'unknown-value', + 'confidence': 'unknown-value', + } + ) + self.assertIsInstance(info, BotInfo) + self.assertEqual(info.identity, 'unknown-value') + self.assertEqual(info.confidence, 'unknown-value') + + def test_event_with_multiple_unknown_enum_values(self) -> None: + """Event should deserialize when multiple enum fields have unknown values.""" + data = self._load_event_json() + data['proxy_details']['proxy_type'] = 'unknown-value' + data['sdk']['platform'] = 'unknown-value' + data['bot'] = 'unknown-value' + data['vpn'] = True + data['vpn_confidence'] = 'unknown-value' + data['proxy_confidence'] = 'unknown-value' + data['tampering_confidence'] = 'unknown-value' + data['rare_device_percentile_bucket'] = 'unknown-value' + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.proxy_details.proxy_type, 'unknown-value') + self.assertEqual(event.sdk.platform, 'unknown-value') + self.assertEqual(event.bot, 'unknown-value') + self.assertEqual(event.vpn_confidence, 'unknown-value') + self.assertEqual(event.proxy_confidence, 'unknown-value') + self.assertEqual(event.tampering_confidence, 'unknown-value') + self.assertEqual(event.rare_device_percentile_bucket, 'unknown-value') + + def test_known_enum_values_still_work(self) -> None: + """Known enum values should continue to work as before.""" + data = self._load_event_json() + + event = Event.from_json(json.dumps(data)) + self.assertIsInstance(event, Event) + self.assertEqual(event.proxy_details.proxy_type, 'residential') + self.assertEqual(event.sdk.platform, 'js') + + +if __name__ == '__main__': + unittest.main()