Неверное указание типа с помощью универсального TypeVar и Pydantic BaseModel.Python

Программы на Python
Ответить
Anonymous
 Неверное указание типа с помощью универсального TypeVar и Pydantic BaseModel.

Сообщение Anonymous »

Я использую Python 3.9 и у меня есть этот код:

Код: Выделить всё

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> где-нибудь еще и импортируем его
Любая помощь приветствуется. Спасибо!

Подробнее здесь: https://stackoverflow.com/questions/792 ... -basemodel
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»