Как определить необязательное поле [Decimal] в библиотеке sqlmodel Python, которое соответствует ограниченной схеме JSON

У меня есть следующий базовый класс sqlmodel и модели таблиц, но мне не удается сделать поле Decimal необязательным, сохранив при этом ограничения. PS: Я уже знаю, что это уже обсуждалось для pydantic, но решение, похоже, не работает с sqlmodel.

  1. Первая попытка (Ошибка: ValueError: Unknown constraint max_digits)
class ItemBase(SQLModel):
  # ...
  price: Optional[Decimal] = Field(
    default=None, max_digits=7, decimal_places=4, ge=0, le=100
  )
  # ...

class ItemTable(ItemBase, table=True):
  __tablename__ = "item"
  # ...
  1. Решение Pydantic говорит, что ограничения следует применять только к десятичному числу (https://github.com/pydantic/pydantic/discussions/7962#discussioncomment-7939114). В sqlmodel возникает ошибка: TypeError: issubclass() arg 1 must be a class
class ItemBase(SQLModel):
  # ...
  price: Optional[Annotated[Decimal, Field(
    default=None, max_digits=7, decimal_places=4, ge=0, le=100
  )]] = None
  # ...
  1. Мое решение для работы кода состояло в том, чтобы использовать sa_column для ограничений max_digits и decimal_places, это нормально, потому что это действительно важно в основном на стороне базы данных.
class ItemBase(SQLModel):
  price: Optional[Decimal] = Field(
    default=None, ge=0, le=100, sa_column=Column(DECIMAL(7,4)),
    regex=r"^\d+(\.\d+)?$"  # added for the string part to only accept numbers
  )

После этой последней попытки ограничения le и ge работают в Python правильно:

ItemTable(..., price=Decimal(123.45))  # => throws ValidationError
ItemTable(..., price=Decimal(34.12))  # => works fine

Но теперь моя проблема в том, что сгенерированная с ее помощью схема json снимает все мои ограничения и становится только: anyOf: [{type: number}, {type: string}, {type: null}]

Проблематично, потому что тогда я использую схему json для создания поддельных объектов (с библиотекой jsf) для своих тестов, и без ограничений все мои тесты терпят неудачу...

Изменить с ответом:

Благодаря @Jeremy я понял, что ошибка, указанная в пункте 2, возникает только для моделей table=True. В итоге я сделал следующее:

class ItemBase(SQLModel):
  # ...
  price: Optional[Annotated[Decimal, Field(
    multiple_of=0.0001, ge=0, le=100
  )]] = None
  # ...

class ItemTable(ItemBase, table=True):
  price: Optional[Decimal] = Field(
    default=None, ge=0, le=100, sa_column=Column(DECIMAL(7,4)
  )

Чтобы добавить ограничение шаблона к строковому типу десятичной модели json, мне пришлось создать подкласс pydantic GenerateJsonSchema:

class MySchemaGenerator(GenerateJsonSchema):
  def decimal_schema(self, schema: core_schema.DecimalSchema) -> JsonSchemaValue:
    json_schema = self.str_schema(core_schema.str_schema(pattern=r"^[-+]?\d+((?:\.\d+)|(?:[eE][-+]?\d+))?$"))
    ... # the rest of the method is unchanged

ItemBase.model_json_schema(schema_generator=MySchemaGenerator)
"""returns for 'price' property:
{'price': {'anyOf': [{'maximum': 100.0,
     'minimum': 0.0,
     'multipleOf': 0.0001,
     'type': 'number'},
    {'pattern': '^[-+]?\\d+((?:\\.\\d+)|(?:[eE][-+]?\\d+))?$',
     'type': 'string'},
    {'type': 'null'}],
   'default': None,
   'title': 'Price'}
"""

Но таким образом я не могу указать шаблон непосредственно в поле, поэтому все десятичные дроби будут иметь один и тот же шаблон. Если у кого-то есть идея, как передать регулярное выражение из определения поля в этот decimal_schema метод, я бы с радостью им воспользовался!

Какую версию sqlmodel вы используете?

DurandA 09.04.2024 19:03

Версии следующие: sqlmodel==0.0.16 и pydantic==2.6.4.

Joachim Huet 09.04.2024 19:14
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
284
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуй это

  • 0 эквивалентно default = 0
  • multiple_of дает вам требование точности как делимое число
  • le дает максимальное целое значение
class ItemBase(SQLModel)
  price: Optional[Annotated[Decimal, Field(0, multiple_of=0.0001, le=1000000)]] | None

он должен создать примерно такую ​​схему

{
    "type": [
        "object",
        "null"
    ],
    "properties": {
        "price": {
            "type": "number",
            "maximum": 1000000,
            "multipleOf": 0.0001
        }
    }
}

Это позволит проверить число до 7 цифр, а если указано десятичное значение, должно быть проверено до четырех (4) точек точности. 456123.1235

Если это по-прежнему не помогло, есть немало ответов SO, указывающих на то, что пакет typing-extensions, возможно, необходимо обновить.

pip install --force-reinstall typing-extensions==4.5.0

Я не копал глубже, но мне интересно, испытывает ли pydantic какие-либо трудности с генерацией схемы JSON из-за конфликта ключевых слов между определенным типом и ограничениями, поскольку что-то вроде max_digits эквивалентно maxLength, которое является ограничением для значений type: string.

Спасибо, это помогло мне понять немного больше. Кажется, моя проблема issubclass() arg 1 must be a class связана с sql alchemy, когда я устанавливаю table=True. typing-extensions последняя версия кажется 4.11.0 однако. И проблема для меня действительно заключалась в том, что ограничения, какими бы они ни были, были просто удалены. Если вы можете дополнительно рассказать мне, как добавить регулярное выражение в виде строкового шаблона? (:

Joachim Huet 12.04.2024 08:35

Я понял, как это сделать, переопределение метода GenerateJsonSchema.decimal_schema помогло. Я дам полный ответ на свой вопрос. Спасибо за вашу помощь в указании мне в правильном направлении.

Joachim Huet 12.04.2024 10:09

Другие вопросы по теме