from typing import Any, ClassVar, Generic, Optional, Type, TypeVar
from babel import Locale
from injectable import injectable
from pydantic import BaseModel
class TranslatableMeta(type):
def __new__(cls, name, bases, dct):
if "translatable_fields" not in dct:
raise TypeError(f"Class {name} must define a 'translatable_fields' as a ClassVar[set[str]]")
if not isinstance(dct["translatable_fields"], set) or not all(
isinstance(field, str) for field in dct["translatable_fields"]
):
raise TypeError(f"Class {name}: 'translatable_fields' must be a ClassVar[set[str]]")
model_fields = dct.get("__annotations__", {})
invalid_fields = [field for field in dct["translatable_fields"] if field not in model_fields]
if invalid_fields:
raise TypeError(f"Class {name} has invalid translatable fields: {', '.join(invalid_fields)}")
return super().__new__(cls, name, bases, dct)
class TranslatablePydanticMeta(type(BaseModel), TranslatableMeta):
pass
class TranslatableModel(BaseModel, metaclass=TranslatablePydanticMeta):
translatable_fields: ClassVar[set[str]] = set()
T = TypeVar("T", bound=TranslatableModel)
@injectable
class ModelTranslator(Generic[T]):
def translate(
self,
model: Type[T], # Model class
base_fields: dict[str, Any],
field_translations: dict[Locale, dict[str, Any]],
locales: list[Locale],
) -> Optional[T]:
for locale in locales:
translations = field_translations.get(locale, {})
model_data = {**base_fields}
translated_model_instance = self.__translate(model, model_data, translations)
if translated_model_instance is not None:
return translated_model_instance
return None
def __translate(self, model: Type[T], base_fields: dict[str, Any], translations: dict[str, Any]) -> Optional[T]:
for field_name, translation in translations.items():
if field_name in model.translatable_fields:
base_fields[field_name] = translation
try:
return model.model_validate(base_fields)
except ValueError:
return None
class ExampleModel(TranslatableModel):
field1: str
field2: int
field3: str
field4: Optional[str] = None
translatable_fields = {"field1", "field3", "field2"}
base_fields = {
"field2": 1,
}
field_translations = {
Locale("en"): {
"field1": "field1_en",
"field3": "field3_en",
},
Locale("es"): {
"field1": "field1_es",
"field3": "field3_es",
},
}
locales = [Locale("en"), Locale("es")]
result = ModelTranslator().translate(ExampleModel, base_fields, field_translations, locales)
Он работает правильно и возвращает в результате пример модели. Однако линтинг в Pycharm не работает. Он сообщает мне, что результат имеет тип Необязательный[TranslatableModel], тогда как выходные данные на самом деле имеют тип Необязательный[ExampleModel].
Интересно Дело в том, что если я определяю T в начале, подсказка типа в Pycharm работает:
from typing import Any, ClassVar, Generic, Optional, Type, TypeVar
from babel import Locale
from injectable import injectable
from pydantic import BaseModel
T = TypeVar("T", bound=TranslatableModel)
class TranslatableMeta(type):
def __new__(cls, name, bases, dct):
if "translatable_fields" not in dct:
raise TypeError(f"Class {name} must define a 'translatable_fields' as a ClassVar[set[str]]")
if not isinstance(dct["translatable_fields"], set) or not all(
isinstance(field, str) for field in dct["translatable_fields"]
):
raise TypeError(f"Class {name}: 'translatable_fields' must be a ClassVar[set[str]]")
model_fields = dct.get("__annotations__", {})
invalid_fields = [field for field in dct["translatable_fields"] if field not in model_fields]
if invalid_fields:
raise TypeError(f"Class {name} has invalid translatable fields: {', '.join(invalid_fields)}")
return super().__new__(cls, name, bases, dct)
class TranslatablePydanticMeta(type(BaseModel), TranslatableMeta):
pass
class TranslatableModel(BaseModel, metaclass=TranslatablePydanticMeta):
translatable_fields: ClassVar[set[str]] = set()
@injectable
class ModelTranslator(Generic[T]):
def translate(
self,
model: Type[T], # Model class
base_fields: dict[str, Any],
field_translations: dict[Locale, dict[str, Any]],
locales: list[Locale],
) -> Optional[T]:
for locale in locales:
translations = field_translations.get(locale, {})
model_data = {**base_fields}
translated_model_instance = self.__translate(model, model_data, translations)
if translated_model_instance is not None:
return translated_model_instance
return None
def __translate(self, model: Type[T], base_fields: dict[str, Any], translations: dict[str, Any]) -> Optional[T]:
for field_name, translation in translations.items():
if field_name in model.translatable_fields:
base_fields[field_name] = translation
try:
return model.model_validate(base_fields)
except ValueError:
return None
class ExampleModel(TranslatableModel):
field1: str
field2: int
field3: str
field4: Optional[str] = None
translatable_fields = {"field1", "field3", "field2"}
base_fields = {
"field2": 1,
}
field_translations = {
Locale("en"): {
"field1": "field1_en",
"field3": "field3_en",
},
Locale("es"): {
"field1": "field1_es",
"field3": "field3_es",
},
}
locales = [Locale("en"), Locale("es")]
result = ModelTranslator().translate(ExampleModel, base_fields, field_translations, locales)
Таким образом он сообщает мне, что результат имеет тип Необязательный[ExampleModel]. Однако когда я пытаюсь запустить код, я получаю следующую ошибку (как и ожидалось):
NameError: name 'TranslatableModel' is not defined
Я пробовал множество вариантов, включая комбинации:
Использование аннотаций импорта from __future__
Поместите T перед определением TranslatableModel и используйте T = TypeVar("T",bound="TranslatableModel")
Определение TranslatableModel< /code> где-нибудь еще и импортируем его
Я использую Python 3.9 и у меня есть этот код: [code]from typing import Any, ClassVar, Generic, Optional, Type, TypeVar
from babel import Locale from injectable import injectable from pydantic import BaseModel
class TranslatableMeta(type): def __new__(cls, name, bases, dct): if "translatable_fields" not in dct: raise TypeError(f"Class {name} must define a 'translatable_fields' as a ClassVar[set[str]]")
if not isinstance(dct["translatable_fields"], set) or not all( isinstance(field, str) for field in dct["translatable_fields"] ): raise TypeError(f"Class {name}: 'translatable_fields' must be a ClassVar[set[str]]")
model_fields = dct.get("__annotations__", {}) invalid_fields = [field for field in dct["translatable_fields"] if field not in model_fields] if invalid_fields: raise TypeError(f"Class {name} has invalid translatable fields: {', '.join(invalid_fields)}")
return super().__new__(cls, name, bases, dct)
class TranslatablePydanticMeta(type(BaseModel), TranslatableMeta): pass
class TranslatableModel(BaseModel, metaclass=TranslatablePydanticMeta): translatable_fields: ClassVar[set[str]] = set()
T = TypeVar("T", bound=TranslatableModel)
@injectable class ModelTranslator(Generic[T]): def translate( self, model: Type[T], # Model class base_fields: dict[str, Any], field_translations: dict[Locale, dict[str, Any]], locales: list[Locale], ) -> Optional[T]: for locale in locales: translations = field_translations.get(locale, {}) model_data = {**base_fields} translated_model_instance = self.__translate(model, model_data, translations) if translated_model_instance is not None: return translated_model_instance return None
def __translate(self, model: Type[T], base_fields: dict[str, Any], translations: dict[str, Any]) -> Optional[T]: for field_name, translation in translations.items(): if field_name in model.translatable_fields: base_fields[field_name] = translation try: return model.model_validate(base_fields) except ValueError: return None
class ExampleModel(TranslatableModel): field1: str field2: int field3: str field4: Optional[str] = None
result = ModelTranslator().translate(ExampleModel, base_fields, field_translations, locales) [/code] Он работает правильно и возвращает в результате пример модели. Однако линтинг в Pycharm не работает. Он сообщает мне, что результат имеет тип Необязательный[TranslatableModel], тогда как выходные данные на самом деле имеют тип Необязательный[ExampleModel]. Интересно Дело в том, что если я определяю T в начале, подсказка типа в Pycharm работает: [code]from typing import Any, ClassVar, Generic, Optional, Type, TypeVar
from babel import Locale from injectable import injectable from pydantic import BaseModel
T = TypeVar("T", bound=TranslatableModel)
class TranslatableMeta(type): def __new__(cls, name, bases, dct): if "translatable_fields" not in dct: raise TypeError(f"Class {name} must define a 'translatable_fields' as a ClassVar[set[str]]")
if not isinstance(dct["translatable_fields"], set) or not all( isinstance(field, str) for field in dct["translatable_fields"] ): raise TypeError(f"Class {name}: 'translatable_fields' must be a ClassVar[set[str]]")
model_fields = dct.get("__annotations__", {}) invalid_fields = [field for field in dct["translatable_fields"] if field not in model_fields] if invalid_fields: raise TypeError(f"Class {name} has invalid translatable fields: {', '.join(invalid_fields)}")
return super().__new__(cls, name, bases, dct)
class TranslatablePydanticMeta(type(BaseModel), TranslatableMeta): pass
class TranslatableModel(BaseModel, metaclass=TranslatablePydanticMeta): translatable_fields: ClassVar[set[str]] = set()
@injectable class ModelTranslator(Generic[T]): def translate( self, model: Type[T], # Model class base_fields: dict[str, Any], field_translations: dict[Locale, dict[str, Any]], locales: list[Locale], ) -> Optional[T]: for locale in locales: translations = field_translations.get(locale, {}) model_data = {**base_fields} translated_model_instance = self.__translate(model, model_data, translations) if translated_model_instance is not None: return translated_model_instance return None
def __translate(self, model: Type[T], base_fields: dict[str, Any], translations: dict[str, Any]) -> Optional[T]: for field_name, translation in translations.items(): if field_name in model.translatable_fields: base_fields[field_name] = translation try: return model.model_validate(base_fields) except ValueError: return None
class ExampleModel(TranslatableModel): field1: str field2: int field3: str field4: Optional[str] = None
result = ModelTranslator().translate(ExampleModel, base_fields, field_translations, locales) [/code] Таким образом он сообщает мне, что результат имеет тип Необязательный[ExampleModel]. Однако когда я пытаюсь запустить код, я получаю следующую ошибку (как и ожидалось): [code]NameError: name 'TranslatableModel' is not defined [/code] Я пробовал множество вариантов, включая комбинации: [list] [*]Использование аннотаций импорта from __future__ Поместите T перед определением TranslatableModel и используйте T = TypeVar("T",bound="TranslatableModel") [*]Определение TranslatableModel< /code> где-нибудь еще и импортируем его [/list] Любая помощь приветствуется. Спасибо!