Задать вопрос
@firstmixon

Как в питоне обеспечить контроль целосности данных?

Есть в питоне целочисленный тип Int, который имеет достаточно условные границы в типовых задачах, как обеспечить сохранение данных в БД, в случае описания целочисленных полей в виде (byte, short, word, int, long), когда я пытаюсь Insert\Update field_byte=300?
В типизированных языках этот вопрос решался бы через объявления переменной field_byte как byte и ошибка на строке field_byte=100*3
  • Вопрос задан
  • 160 просмотров
Подписаться 1 Простой 1 комментарий
Решения вопроса 1
Vindicar
@Vindicar
RTFM!
С переменными так не прокатит. А вот с полями класса - при желании можно, если использовать property.
Базовая идея простая - пусть сеттер свойства производит валидацию.
Например, так
class Person:
    def __init__(self, name: str):
        self._name: str = ''
        self.name = name
    
    @property
    def name(self) -> str:
        return self._name
    
    @name.setter
    def name(self, value: str) -> None:
        if not value:
            raise ValueError('Must not be empty')
        self._name = value

Но писать такой бойлерплейт быстро надоест. Поэтому можем попробовать обобщить процесс.
Например, так
from typing import TypeVar, Type, Any, Annotated, Callable, Dict, get_args, get_origin

T = TypeVar('T')
ValidationRule = Callable[[Any], bool]
ValidationRules = Dict[str, ValidationRule]

class ValidatedProperty:
    """Экземпляры этого класса будут свойствами в валидируемых классах, и будут заниматься валидацией."""
    def __init__(self, name: str, storagename: str, rules: ValidationRules):
        self.name = name  # как называется свойство
        self.storagename = storagename  # где его хранить
        self.rules = rules  # какие правила применять

    def __get__(self, instance, owner=None):
        return getattr(instance, self.storagename)  # при чтении просто возвращаем свойство
    
    def __set__(self, instance, value):  # при записи валидируем
        for message, rule in self.rules.items():  
            if not rule(value):  # если правило нарушено, выкидываем исключение с сообщением
                raise ValueError(f'{instance.__class__.__name__}.{self.name}: {message}')
        setattr(instance, self.storagename, value)


def validated(klass: Type[T]) -> Type[T]:
    """Декоратор для валидируемых классов."""
    for name, annot in klass.__annotations__.items():  # проверяем список аннотаций в классе
        base = get_origin(annot) or annot
        if base is not Annotated:  # нас интересуют только те, которые помечены как Annotated
            continue
        args = get_args(annot)
        rules = [arg for arg in args if isinstance(arg, dict)]
        if not rules:  # и только если один (любой) из аргументов Annotated - словарь
            continue
        # в этом случае мы считаем, что словарь содержит правила валидации, и создаём свойство класса
        setattr(klass, name, ValidatedProperty(name, f'_{name}', rules[0]))
    return klass  # не забываем вернуть изменённый класс!


@validated
class Person:
    name: Annotated[str, {'must not be empty': lambda v: bool(v)}]
    age: Annotated[int, {'must be positive': lambda v: v > 0}]

    def __init__(self, name: str, age: int):
        self.name = name  # валидация отработает уже здесь
        self.age = age  # валидация отработает уже здесь
    
    def __repr__(self) -> str:
        return f'Person(name={self.name!r}, age={self.age!r})'

try:
    Person('John Doe', 23)  # отработает успешно
except Exception as err:
    print('Failed to create person 1')
    print(f'{err.__class__.__name__}: {err!s}')
else:
    print('Person 1 created')

try:
    Person('', 23)  # выкинет исключение
except Exception as err:
    print('Failed to create person 2')
    print(f'{err.__class__.__name__}: {err!s}')
else:
    print('Person 2 created')

try:
    Person('Jane Doe', -23)  # выкинет исключение
except Exception as err:
    print('Failed to create person 3')
    print(f'{err.__class__.__name__}: {err!s}')
else:
    print('Person 3 created')

p = Person('John Doe', 23)
try:
    p.name = ''  # выкинет исключение
except Exception as err:
    print('Failed to modify person')
    print(f'{err.__class__.__name__}: {err!s}')

try:
    p.age = 0  # выкинет исключение
except Exception as err:
    print('Failed to modify person')
    print(f'{err.__class__.__name__}: {err!s}')

try:
    p.age = 24  # отработает успешно
except Exception as err:
    print('Failed to modify person')
    print(f'{err.__class__.__name__}: {err!s}')

print(p)
print(vars(p))


Это, конечно, велосипед, и пользоваться им я бы не посоветовал. Для валидации есть библиотеки типа pydantic.
Плюс, как было указано выше, не всё и не всегда имеет смысл валидировать...

А для работы с БД лучше использовать ORM. SqlAlchemy популярна, но есть и более простые, например, peewee или ponyorm.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
phaggi
@phaggi Куратор тега Python
лужу, паяю, ЭВМы починяю
Как-то так?

my_int_var = 555555
print(isinstance(my_int_var, int))
my_int_var = 555555.5
print(isinstance(my_int_var, int))
my_int_var = '555555'
print(isinstance(my_int_var, int))
my_int_var = True
print(isinstance(my_int_var, int))
my_int_var = b'True'
print(isinstance(my_int_var, int))

Может быть только проблема с bool, поскольку оно мэппится с 1/0.
Ответ написан
Комментировать
@semenovs
QA mobile, bh
Нестандартное решение:
from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyTable(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    field_byte = Column(Integer)
    field_short = Column(Integer)
    # Другие поля

# Пример использования
value = 300

# Создание экземпляра класса
my_object = MyTable(field_byte=value)

# Вставка в базу данных
session.add(my_object)
session.commit()


В классе определены поля с явно заданными типами данных. Если вы попытаетесь присвоить значение, выходящее за допустимый диапазон, SQLAlchemy сгенерирует исключение.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы