From 76440e3b7b732bb8472f0d39177dd6a7a50ef5b6 Mon Sep 17 00:00:00 2001 From: Pocoder Date: Tue, 31 Jan 2023 22:54:43 +0100 Subject: [PATCH 1/7] don't raise exception on unknown variant spec' --- src/client/primitives/base.py | 31 ++++++++++++++++++++++++++----- tests/utils/test_variant_type.py | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/client/primitives/base.py b/src/client/primitives/base.py index 82a11267..ad11d326 100644 --- a/src/client/primitives/base.py +++ b/src/client/primitives/base.py @@ -9,7 +9,7 @@ import inspect import typing -from copy import copy +from copy import copy, deepcopy from enum import Enum from functools import update_wrapper, partial from typing import Any, ClassVar, Dict, List, Optional, Type, TypeVar, Union, Tuple @@ -192,6 +192,25 @@ def __init_subclass__(cls, spec_enum: Optional[Union[str, Type[E]]] = None, enum = getattr(cls, spec_enum) if isinstance(spec_enum, str) else spec_enum cls._variant_registry = VariantRegistry(spec_field, enum) + class _UnknownVariant: + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + @classmethod + def structure(cls, data: Any): + return cls(**data) + + def unstructure(self): + data = deepcopy(self.__dict__) + data.update(converter.unstructure(cls.get_variant_specs())) + return data + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + cls._variant_registry.registered_classes['_unknown_variant'] = _UnknownVariant + # Unexpected fields access def __getattr__(self, item): @@ -221,7 +240,7 @@ def get_variant_specs(cls) -> dict: variant_specs = {} for base in cls.__mro__: registry = base.__dict__.get('_variant_registry') - if registry: + if registry and hasattr(cls, registry.field): variant_specs[registry.field] = getattr(cls, registry.field) return variant_specs @@ -262,13 +281,15 @@ def structure(cls, data: Any): # TODO: Optimize copying data = dict(data) # Do not modify input data spec_field = cls._variant_registry.field - data_field = data.pop(spec_field) + data_field = data[spec_field] try: spec_value = cls._variant_registry.enum(data_field) spec_class = cls._variant_registry.registered_classes[spec_value] + del data[spec_field] except Exception: - raise SpecClassIdentificationError(spec_field=spec_field, - spec_enum=cls._variant_registry.enum.__name__) + spec_class = cls._variant_registry.registered_classes['_unknown_variant'] + # raise SpecClassIdentificationError(spec_field=spec_field, + # spec_enum=cls._variant_registry.enum.__name__) return spec_class.structure(data) data = copy(data) diff --git a/tests/utils/test_variant_type.py b/tests/utils/test_variant_type.py index 20907d50..7659500a 100644 --- a/tests/utils/test_variant_type.py +++ b/tests/utils/test_variant_type.py @@ -92,6 +92,33 @@ def test_structure_variant(): converter.structure({'type': 'list', 'method': 'pop', 'argument': 'abc'}) +def test_structure_unknown_variant(): + unstructured_with_unknown_high_level_variant = {'type': 'dict', 'method': 'pop'} + unstructured_with_unknown_low_level_variant = {'type': 'list', 'method': 'index'} + unstructured_with_unknown_both_levels_variants = {'type': 'dict', 'method': 'get'} + + method_call_with_unknown_high_level_variant = converter.structure(unstructured_with_unknown_high_level_variant, + MethodCall) + method_call_with_unknown_low_level_variant = converter.structure(unstructured_with_unknown_low_level_variant, + MethodCall) + method_call_with_unknown_both_levels_variants = converter.structure(unstructured_with_unknown_both_levels_variants, + MethodCall) + + assert MethodCall._variant_registry['_unknown_variant'](**unstructured_with_unknown_high_level_variant) \ + == method_call_with_unknown_high_level_variant + assert ListMethodCall._variant_registry['_unknown_variant'](method='index') \ + == method_call_with_unknown_low_level_variant + assert MethodCall._variant_registry['_unknown_variant'](**unstructured_with_unknown_both_levels_variants) == \ + method_call_with_unknown_both_levels_variants + + assert converter.unstructure( + method_call_with_unknown_high_level_variant) == unstructured_with_unknown_high_level_variant + assert converter.unstructure( + method_call_with_unknown_low_level_variant) == unstructured_with_unknown_low_level_variant + assert converter.unstructure( + method_call_with_unknown_both_levels_variants) == unstructured_with_unknown_both_levels_variants + + def test_unstructure_variant(): assert {'type': 'set', 'method': 'pop'} == converter.unstructure(SetPopMethodCall()) assert {'type': 'list', 'method': 'pop'} == converter.unstructure(ListPopMethodCall()) From dff4cdd60650a3504eeb71d129b0741f0141f9cd Mon Sep 17 00:00:00 2001 From: Pocoder Date: Wed, 1 Feb 2023 15:00:51 +0100 Subject: [PATCH 2/7] make variant registry extendable --- src/client/primitives/base.py | 61 ++++++++++++++++---------------- tests/utils/test_variant_type.py | 20 +++++------ 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/client/primitives/base.py b/src/client/primitives/base.py index ad11d326..3bde19a6 100644 --- a/src/client/primitives/base.py +++ b/src/client/primitives/base.py @@ -9,7 +9,7 @@ import inspect import typing -from copy import copy, deepcopy +from copy import copy from enum import Enum from functools import update_wrapper, partial from typing import Any, ClassVar, Dict, List, Optional, Type, TypeVar, Union, Tuple @@ -29,10 +29,11 @@ class VariantRegistry: - def __init__(self, field: str, enum: Type[E]): + def __init__(self, field: str, enum: Type[E], extendable: bool = False): self.field: str = field self.enum: Type[E] = enum self.registered_classes: Dict[E, type] = {} + self.extendable = extendable def register(self, type_: type, value: E) -> type: @@ -47,6 +48,15 @@ def register(self, type_: type, value: E) -> type: return type_ + def generate_subtype(self, type_: type, value: E) -> type: + if not self.extendable: + raise NotImplementedError() + + class _UnknownVariant(type_, spec_value=value): + ... + + return self.registered_classes[value] + def __getitem__(self, value: E): return self.registered_classes[value] @@ -171,13 +181,21 @@ def __new__(cls, *args, **kwargs): return super().__new__(cls) @classmethod - def __init_subclass__(cls, spec_enum: Optional[Union[str, Type[E]]] = None, - spec_field: Optional[str] = None, spec_value=None): + def __init_subclass__( + cls, + spec_enum: Optional[Union[str, Type[E]]] = None, + spec_field: Optional[str] = None, + spec_value=None, + extend_spec: bool = False, + ): super().__init_subclass__() # Completing a variant type if spec_value is not None: cls._variant_registry.register(cls, spec_value) + if extend_spec and (spec_enum is None or spec_field is None): + raise ValueError('extend_spec could be True only with spec_enum and spec_field provided') + # Making into a variant type if spec_enum is not None or spec_field is not None: @@ -190,26 +208,7 @@ def __init_subclass__(cls, spec_enum: Optional[Union[str, Type[E]]] = None, # TODO: Possibly make it immutable enum = getattr(cls, spec_enum) if isinstance(spec_enum, str) else spec_enum - cls._variant_registry = VariantRegistry(spec_field, enum) - - class _UnknownVariant: - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - @classmethod - def structure(cls, data: Any): - return cls(**data) - - def unstructure(self): - data = deepcopy(self.__dict__) - data.update(converter.unstructure(cls.get_variant_specs())) - return data - - def __eq__(self, other): - return self.__dict__ == other.__dict__ - - cls._variant_registry.registered_classes['_unknown_variant'] = _UnknownVariant + cls._variant_registry = VariantRegistry(spec_field, enum, extend_spec) # Unexpected fields access @@ -281,15 +280,17 @@ def structure(cls, data: Any): # TODO: Optimize copying data = dict(data) # Do not modify input data spec_field = cls._variant_registry.field - data_field = data[spec_field] + data_field = data.pop(spec_field) try: spec_value = cls._variant_registry.enum(data_field) - spec_class = cls._variant_registry.registered_classes[spec_value] - del data[spec_field] + + if spec_value in cls._variant_registry.registered_classes: + spec_class = cls._variant_registry[spec_value] + else: + spec_class = cls._variant_registry.generate_subtype(cls, spec_value) except Exception: - spec_class = cls._variant_registry.registered_classes['_unknown_variant'] - # raise SpecClassIdentificationError(spec_field=spec_field, - # spec_enum=cls._variant_registry.enum.__name__) + raise SpecClassIdentificationError(spec_field=spec_field, + spec_enum=cls._variant_registry.enum.__name__) return spec_class.structure(data) data = copy(data) diff --git a/tests/utils/test_variant_type.py b/tests/utils/test_variant_type.py index 7659500a..3cba4d48 100644 --- a/tests/utils/test_variant_type.py +++ b/tests/utils/test_variant_type.py @@ -4,20 +4,21 @@ import pytest from toloka.client._converter import converter from toloka.client.primitives.base import BaseTolokaObject +from toloka.util._extendable_enum import ExtendableStrEnum @unique -class ItemType(Enum): +class ItemType(ExtendableStrEnum): LIST = 'list' SET = 'set' @unique -class SetMethod(Enum): +class SetMethod(ExtendableStrEnum): POP = 'pop' -class MethodCall(BaseTolokaObject, spec_enum=ItemType, spec_field='type'): +class MethodCall(BaseTolokaObject, spec_enum=ItemType, spec_field='type', extend_spec=True): pass @@ -29,10 +30,10 @@ class SetPopMethodCall(SetMethodCall, spec_value=SetMethod.POP): pass -class ListMethodCall(MethodCall, spec_value=ItemType.LIST, spec_enum='Method', spec_field='method'): +class ListMethodCall(MethodCall, spec_value=ItemType.LIST, spec_enum='Method', spec_field='method', extend_spec=True): @unique - class Method(Enum): + class Method(ExtendableStrEnum): APPEND = 'append' POP = 'pop' @@ -104,12 +105,9 @@ def test_structure_unknown_variant(): method_call_with_unknown_both_levels_variants = converter.structure(unstructured_with_unknown_both_levels_variants, MethodCall) - assert MethodCall._variant_registry['_unknown_variant'](**unstructured_with_unknown_high_level_variant) \ - == method_call_with_unknown_high_level_variant - assert ListMethodCall._variant_registry['_unknown_variant'](method='index') \ - == method_call_with_unknown_low_level_variant - assert MethodCall._variant_registry['_unknown_variant'](**unstructured_with_unknown_both_levels_variants) == \ - method_call_with_unknown_both_levels_variants + assert isinstance(method_call_with_unknown_high_level_variant, MethodCall) + assert isinstance(method_call_with_unknown_low_level_variant, ListMethodCall) + assert isinstance(method_call_with_unknown_both_levels_variants, MethodCall) assert converter.unstructure( method_call_with_unknown_high_level_variant) == unstructured_with_unknown_high_level_variant From 167914a3ea33e6f77967aafac05ca9e95f7035d6 Mon Sep 17 00:00:00 2001 From: Pocoder Date: Wed, 1 Feb 2023 15:40:35 +0100 Subject: [PATCH 3/7] Generate meaningful name --- src/client/primitives/base.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client/primitives/base.py b/src/client/primitives/base.py index 3bde19a6..297be928 100644 --- a/src/client/primitives/base.py +++ b/src/client/primitives/base.py @@ -17,6 +17,7 @@ import attr import simplejson as json +from ...util._extendable_enum import ExtendableStrEnumMetaclass from .._converter import converter from ..exceptions import SpecClassIdentificationError from ...util._codegen import ( @@ -30,6 +31,8 @@ class VariantRegistry: def __init__(self, field: str, enum: Type[E], extendable: bool = False): + if extendable and not isinstance(enum, ExtendableStrEnumMetaclass): + raise ValueError('VariantRegistry could be extendable only if spec_enum is extendable.') self.field: str = field self.enum: Type[E] = enum self.registered_classes: Dict[E, type] = {} @@ -52,12 +55,12 @@ def generate_subtype(self, type_: type, value: E) -> type: if not self.extendable: raise NotImplementedError() - class _UnknownVariant(type_, spec_value=value): - ... + generated_type_name = '_Generated' + value.value.title() + type_.__name__ + BaseTolokaObjectMetaclass(generated_type_name, (type_,), {}, spec_value=value) return self.registered_classes[value] - def __getitem__(self, value: E): + def __getitem__(self, value: E) -> type: return self.registered_classes[value] @@ -239,7 +242,7 @@ def get_variant_specs(cls) -> dict: variant_specs = {} for base in cls.__mro__: registry = base.__dict__.get('_variant_registry') - if registry and hasattr(cls, registry.field): + if registry: variant_specs[registry.field] = getattr(cls, registry.field) return variant_specs From ad685c9ca4a6c60cf4de98ce697ad29190912306 Mon Sep 17 00:00:00 2001 From: Pocoder Date: Wed, 1 Feb 2023 16:12:15 +0100 Subject: [PATCH 4/7] Make some classes extendable --- src/client/actions.py | 2 +- src/client/collectors.py | 2 +- src/client/conditions.py | 2 +- src/client/filter.py | 4 ++-- src/client/project/field_spec.py | 2 +- src/client/project/template_builder/base.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client/actions.py b/src/client/actions.py index 05e6d180..13f65b1a 100644 --- a/src/client/actions.py +++ b/src/client/actions.py @@ -29,7 +29,7 @@ class RuleType(ExtendableStrEnum): APPROVE_ALL_ASSIGNMENTS = 'APPROVE_ALL_ASSIGNMENTS' -class RuleAction(BaseParameters, spec_enum=RuleType, spec_field='type'): +class RuleAction(BaseParameters, spec_enum=RuleType, spec_field='type', extend_spec=True): """Base class for all actions in quality controls configurations """ diff --git a/src/client/collectors.py b/src/client/collectors.py index 00c70567..24d0ee08 100644 --- a/src/client/collectors.py +++ b/src/client/collectors.py @@ -39,7 +39,7 @@ def _captcha_deprecation_warning(*args, **kwargs): ) -class CollectorConfig(BaseParameters, spec_enum='Type', spec_field='type'): +class CollectorConfig(BaseParameters, spec_enum='Type', spec_field='type', extend_spec=True): """Base class for all collectors. Attributes: diff --git a/src/client/conditions.py b/src/client/conditions.py index 34bad87d..9700f15a 100644 --- a/src/client/conditions.py +++ b/src/client/conditions.py @@ -67,7 +67,7 @@ class RuleConditionKey(ExtendableStrEnum): TOTAL_SUBMITTED_COUNT = 'total_submitted_count' -class RuleCondition(BaseTolokaObject, spec_enum=RuleConditionKey, spec_field='key'): +class RuleCondition(BaseTolokaObject, spec_enum=RuleConditionKey, spec_field='key', extend_spec=True): operator: Any value: Any diff --git a/src/client/filter.py b/src/client/filter.py index e88c7014..7f1f0122 100644 --- a/src/client/filter.py +++ b/src/client/filter.py @@ -176,7 +176,7 @@ def structure(cls, data): @inherit_docstrings -class Profile(Condition, spec_value=Condition.Category.PROFILE, spec_field='key', spec_enum='Key'): +class Profile(Condition, spec_value=Condition.Category.PROFILE, spec_field='key', spec_enum='Key', extend_spec=True): """A base class for a category of filters that use Toloker's profile. """ @@ -197,7 +197,7 @@ class Key(ExtendableStrEnum): @inherit_docstrings -class Computed(Condition, spec_value=Condition.Category.COMPUTED, spec_field='key', spec_enum='Key'): +class Computed(Condition, spec_value=Condition.Category.COMPUTED, spec_field='key', spec_enum='Key', extend_spec=True): """A base class for a category of filters that use connection and client information. """ diff --git a/src/client/project/field_spec.py b/src/client/project/field_spec.py index 31b86902..5a4526d3 100644 --- a/src/client/project/field_spec.py +++ b/src/client/project/field_spec.py @@ -46,7 +46,7 @@ class FieldType(ExtendableStrEnum): ARRAY_JSON = 'array_json' -class FieldSpec(BaseTolokaObject, spec_enum=FieldType, spec_field='type'): +class FieldSpec(BaseTolokaObject, spec_enum=FieldType, spec_field='type', extend_spec=True): """A base class for field specifications used in project's `input_spec` and `output_spec` for input and response data validation specification respectively. Use subclasses of this class defined below to define the data type (string, integer, URL, etc.) and specify diff --git a/src/client/project/template_builder/base.py b/src/client/project/template_builder/base.py index cdd59438..239727d3 100644 --- a/src/client/project/template_builder/base.py +++ b/src/client/project/template_builder/base.py @@ -124,7 +124,7 @@ def structure(cls, data: dict): return super().structure(data) -class BaseComponent(BaseTemplate, spec_enum=ComponentType, spec_field='type'): +class BaseComponent(BaseTemplate, spec_enum=ComponentType, spec_field='type', extend_spec=True): @classmethod def structure(cls, data: dict): From 732c4eec2b975b06f57a915bfb00423919d51aa0 Mon Sep 17 00:00:00 2001 From: Pocoder Date: Wed, 1 Feb 2023 17:13:03 +0100 Subject: [PATCH 5/7] add generate_subtype in reference toc --- docs/en/_includes/toloka-kit-reference-toc.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/en/_includes/toloka-kit-reference-toc.yaml b/docs/en/_includes/toloka-kit-reference-toc.yaml index 436065d6..73a30f73 100644 --- a/docs/en/_includes/toloka-kit-reference-toc.yaml +++ b/docs/en/_includes/toloka-kit-reference-toc.yaml @@ -1012,6 +1012,9 @@ items: - name: VariantRegistry.register href: ../toloka-kit/reference/toloka.client.primitives.base.VariantRegistry.register.md hidden: true + - name: VariantRegistry.generate_subtype + href: ../toloka-kit/reference/toloka.client.primitives.base.VariantRegistry.generate_subtype.md + hidden: true - name: infinite_overlap items: - name: InfiniteOverlapParametersMixin From 0615b90364979e63bda9c841f083662a7773dd9e Mon Sep 17 00:00:00 2001 From: Pocoder Date: Wed, 1 Feb 2023 20:42:26 +0100 Subject: [PATCH 6/7] add info logging about structuring unknown class --- src/client/primitives/base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/primitives/base.py b/src/client/primitives/base.py index 297be928..9b74ac9e 100644 --- a/src/client/primitives/base.py +++ b/src/client/primitives/base.py @@ -8,6 +8,7 @@ ] import inspect +import logging import typing from copy import copy from enum import Enum @@ -27,6 +28,8 @@ E = TypeVar('E', bound=Enum) +logger = logging.getLogger(__file__) + class VariantRegistry: @@ -53,11 +56,13 @@ def register(self, type_: type, value: E) -> type: def generate_subtype(self, type_: type, value: E) -> type: if not self.extendable: - raise NotImplementedError() + raise NotImplementedError("Only extendable VariantRegistry can generate subtype") generated_type_name = '_Generated' + value.value.title() + type_.__name__ BaseTolokaObjectMetaclass(generated_type_name, (type_,), {}, spec_value=value) - + logger.error(f'{generated_type_name} class was generated. Probably it is a new functionality on the platform.\n' + 'If you want it to be supported by toloka-kit faster ' + 'you can make feature request here: https://github.com/Toloka/toloka-kit/issues/new/choose.') return self.registered_classes[value] def __getitem__(self, value: E) -> type: From de18752e644458a2ae7ef564ef6eecda056b095c Mon Sep 17 00:00:00 2001 From: Pocoder Date: Thu, 2 Feb 2023 16:15:18 +0100 Subject: [PATCH 7/7] Infer variant extendability from enum type --- src/client/actions.py | 2 +- src/client/collectors.py | 2 +- src/client/conditions.py | 2 +- src/client/filter.py | 4 ++-- src/client/primitives/base.py | 15 +++++++++------ src/client/project/field_spec.py | 2 +- src/client/project/template_builder/base.py | 2 +- tests/utils/test_variant_type.py | 13 +++++++++---- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/client/actions.py b/src/client/actions.py index 13f65b1a..05e6d180 100644 --- a/src/client/actions.py +++ b/src/client/actions.py @@ -29,7 +29,7 @@ class RuleType(ExtendableStrEnum): APPROVE_ALL_ASSIGNMENTS = 'APPROVE_ALL_ASSIGNMENTS' -class RuleAction(BaseParameters, spec_enum=RuleType, spec_field='type', extend_spec=True): +class RuleAction(BaseParameters, spec_enum=RuleType, spec_field='type'): """Base class for all actions in quality controls configurations """ diff --git a/src/client/collectors.py b/src/client/collectors.py index 24d0ee08..00c70567 100644 --- a/src/client/collectors.py +++ b/src/client/collectors.py @@ -39,7 +39,7 @@ def _captcha_deprecation_warning(*args, **kwargs): ) -class CollectorConfig(BaseParameters, spec_enum='Type', spec_field='type', extend_spec=True): +class CollectorConfig(BaseParameters, spec_enum='Type', spec_field='type'): """Base class for all collectors. Attributes: diff --git a/src/client/conditions.py b/src/client/conditions.py index 9700f15a..34bad87d 100644 --- a/src/client/conditions.py +++ b/src/client/conditions.py @@ -67,7 +67,7 @@ class RuleConditionKey(ExtendableStrEnum): TOTAL_SUBMITTED_COUNT = 'total_submitted_count' -class RuleCondition(BaseTolokaObject, spec_enum=RuleConditionKey, spec_field='key', extend_spec=True): +class RuleCondition(BaseTolokaObject, spec_enum=RuleConditionKey, spec_field='key'): operator: Any value: Any diff --git a/src/client/filter.py b/src/client/filter.py index 7f1f0122..e88c7014 100644 --- a/src/client/filter.py +++ b/src/client/filter.py @@ -176,7 +176,7 @@ def structure(cls, data): @inherit_docstrings -class Profile(Condition, spec_value=Condition.Category.PROFILE, spec_field='key', spec_enum='Key', extend_spec=True): +class Profile(Condition, spec_value=Condition.Category.PROFILE, spec_field='key', spec_enum='Key'): """A base class for a category of filters that use Toloker's profile. """ @@ -197,7 +197,7 @@ class Key(ExtendableStrEnum): @inherit_docstrings -class Computed(Condition, spec_value=Condition.Category.COMPUTED, spec_field='key', spec_enum='Key', extend_spec=True): +class Computed(Condition, spec_value=Condition.Category.COMPUTED, spec_field='key', spec_enum='Key'): """A base class for a category of filters that use connection and client information. """ diff --git a/src/client/primitives/base.py b/src/client/primitives/base.py index 9b74ac9e..7cdb266f 100644 --- a/src/client/primitives/base.py +++ b/src/client/primitives/base.py @@ -33,7 +33,9 @@ class VariantRegistry: - def __init__(self, field: str, enum: Type[E], extendable: bool = False): + def __init__(self, field: str, enum: Type[E], extendable: Optional[bool] = None): + if extendable is None: + extendable = isinstance(enum, ExtendableStrEnumMetaclass) if extendable and not isinstance(enum, ExtendableStrEnumMetaclass): raise ValueError('VariantRegistry could be extendable only if spec_enum is extendable.') self.field: str = field @@ -56,13 +58,14 @@ def register(self, type_: type, value: E) -> type: def generate_subtype(self, type_: type, value: E) -> type: if not self.extendable: - raise NotImplementedError("Only extendable VariantRegistry can generate subtype") + raise NotImplementedError('Only extendable VariantRegistry can generate subtype') generated_type_name = '_Generated' + value.value.title() + type_.__name__ BaseTolokaObjectMetaclass(generated_type_name, (type_,), {}, spec_value=value) - logger.error(f'{generated_type_name} class was generated. Probably it is a new functionality on the platform.\n' - 'If you want it to be supported by toloka-kit faster ' - 'you can make feature request here: https://github.com/Toloka/toloka-kit/issues/new/choose.') + logger.info(f'{generated_type_name} class was generated. ' + f'Probably it is a new functionality on the platform.\n' + f'If you want it to be supported by toloka-kit faster you can make feature request here:' + f'https://github.com/Toloka/toloka-kit/issues/new/choose.') return self.registered_classes[value] def __getitem__(self, value: E) -> type: @@ -194,7 +197,7 @@ def __init_subclass__( spec_enum: Optional[Union[str, Type[E]]] = None, spec_field: Optional[str] = None, spec_value=None, - extend_spec: bool = False, + extend_spec: Optional[bool] = None, ): super().__init_subclass__() # Completing a variant type diff --git a/src/client/project/field_spec.py b/src/client/project/field_spec.py index 5a4526d3..31b86902 100644 --- a/src/client/project/field_spec.py +++ b/src/client/project/field_spec.py @@ -46,7 +46,7 @@ class FieldType(ExtendableStrEnum): ARRAY_JSON = 'array_json' -class FieldSpec(BaseTolokaObject, spec_enum=FieldType, spec_field='type', extend_spec=True): +class FieldSpec(BaseTolokaObject, spec_enum=FieldType, spec_field='type'): """A base class for field specifications used in project's `input_spec` and `output_spec` for input and response data validation specification respectively. Use subclasses of this class defined below to define the data type (string, integer, URL, etc.) and specify diff --git a/src/client/project/template_builder/base.py b/src/client/project/template_builder/base.py index 239727d3..cdd59438 100644 --- a/src/client/project/template_builder/base.py +++ b/src/client/project/template_builder/base.py @@ -124,7 +124,7 @@ def structure(cls, data: dict): return super().structure(data) -class BaseComponent(BaseTemplate, spec_enum=ComponentType, spec_field='type', extend_spec=True): +class BaseComponent(BaseTemplate, spec_enum=ComponentType, spec_field='type'): @classmethod def structure(cls, data: dict): diff --git a/tests/utils/test_variant_type.py b/tests/utils/test_variant_type.py index 3cba4d48..dc37e933 100644 --- a/tests/utils/test_variant_type.py +++ b/tests/utils/test_variant_type.py @@ -2,8 +2,9 @@ from typing import Any, Optional import pytest -from toloka.client._converter import converter +from toloka.client.exceptions import SpecClassIdentificationError from toloka.client.primitives.base import BaseTolokaObject +from toloka.client._converter import converter from toloka.util._extendable_enum import ExtendableStrEnum @@ -14,11 +15,11 @@ class ItemType(ExtendableStrEnum): @unique -class SetMethod(ExtendableStrEnum): +class SetMethod(Enum): POP = 'pop' -class MethodCall(BaseTolokaObject, spec_enum=ItemType, spec_field='type', extend_spec=True): +class MethodCall(BaseTolokaObject, spec_enum=ItemType, spec_field='type'): pass @@ -30,7 +31,7 @@ class SetPopMethodCall(SetMethodCall, spec_value=SetMethod.POP): pass -class ListMethodCall(MethodCall, spec_value=ItemType.LIST, spec_enum='Method', spec_field='method', extend_spec=True): +class ListMethodCall(MethodCall, spec_value=ItemType.LIST, spec_enum='Method', spec_field='method'): @unique class Method(ExtendableStrEnum): @@ -116,6 +117,10 @@ def test_structure_unknown_variant(): assert converter.unstructure( method_call_with_unknown_both_levels_variants) == unstructured_with_unknown_both_levels_variants + with pytest.raises(SpecClassIdentificationError): + unstructured_unextendable = {'type': 'set', 'method': 'new_method'} + converter.structure(unstructured_unextendable, MethodCall) + def test_unstructure_variant(): assert {'type': 'set', 'method': 'pop'} == converter.unstructure(SetPopMethodCall())