I am trying to use the sqlalchemy enum type without creating a native DB ENUM column but still having sqlalchemy validates enum inputs.
So I have this declaration :
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import Enum as SaEnum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import validates
import enum
Base = declarative_base()
class DummyEnum(enum.Enum):
NAME_1 = "NAME_1"
NAME_2 = "NAME_2"
class SAEnumVarcharTable(Base):
__tablename__ = "test_table"
id = Column(Integer, primary_key=True)
uut_column = Column(SaEnum(DummyEnum, native_enum=False, validate_strings=True), default=DummyEnum.NAME_1)
#validates('uut_column')
def validate_uut_column(self, key, uut_value):
return uut_value.upper()
I want the validation returning the string in upper case and only then do the Enum validation...
Is it possible?
because when I do :
connection.execute(insert(SAEnumVarcharTable), {"uut_column" : "name_1"})
it does raise a LookupError where I would expect to be accepted as valid input (first goes to the validates functions -> return an uppercase string -> do the Enum validation)...Is it somehow possible?
Validators are only called when modifying an instance's attributes, e.g.:
instance = SAEnumVarcharTable()
instance.uut_column = "name_1"
In your case, you would need to override sqlalchemy.Enum's _db_value_for_elem:
class UpperCaseEnum(SaEnum):
def _db_value_for_elem(self, elem):
return super()._db_value_for_elem(elem.upper())
# uut_column = Column(SaEnum(...), ...)
uut_column = Column(UpperCaseEnum(...), ...)
Aside
Validator implementation
validate_uut_column is not implemented correctly.
instance = SAEnumVarcharTable()
instance.uut_column = "name_0" # No error, expected LookupError
instance.uut_column = "name_1" # Ok
print(instance.uut_column == DummyEnum.NAME_1) # False, expected True
print(instance.uut_column.value == "NAME_1") # AttributeError: 'str' object has no attribute 'value'
instance.uut_column = DummyEnum.NAME_1 # AttributeError: 'DummyEnum' object has no attribute 'upper'
Instead, it should be:
#validates('uut_column')
def validate_uut_column(self, key, uut_value):
# return uut_value.upper()
return uut_value if uut_value in DummyEnum else DummyEnum[uut_value.upper()]
instance = SAEnumVarcharTable()
instance.uut_column = "name_0" # LookupError, as expected
instance.uut_column = "name_1" # Ok
print(instance.uut_column == DummyEnum.NAME_1) # True, as expected
print(instance.uut_column.value == "NAME_1") # True, as expected
instance.validate_uut_column = DummyEnum.NAME_1 # Ok
Related
Please help.
I try to use SQLAlchemy with dataclasses.
Here is the code.
from dataclasses import fields
from sqlalchemy import Column
from sqlalchemy import Integer, Boolean, String
from sqlalchemy import MetaData
from sqlalchemy import Table
from sqlalchemy.orm import registry
from models import Project, Model, Test
TABLES: dict = {}
#dataclass
class Test:
identifier: int = field(init=False)
name: str = None
def create_table(
sql_mapper_registry: registry, sql_metadata: MetaData, model: type[Test]
) -> None:
table_name: str = model.__name__.lower() + "_table"
TABLES[table_name] = Table(table_name, sql_metadata)
for i in range(len(fields(model))):
if fields(model)[i].type == type(bool()):
type_var = Boolean
elif fields(model)[i].type == type(int()):
type_var = Integer
elif fields(model)[i].type == type(str()):
type_var = String(60)
else:
raise TypeError
TABLES[table_name].append_column(
Column(
fields(model)[i].name,
type_var,
primary_key=(fields(model)[i].name == "identifier"),
)
)
sql_mapper_registry.map_imperatively(
model,
TABLES[table_name],
)
return None
if __name__ == '__main__':
engine = create_engine("mariadb+mariadbconnector://root:Password#127.0.0.1:3306/company")
metadata_obj.create_all(engine)
session = orm.sessionmaker(bind=engine)()
create_table(mapper_registry, metadata_obj, Test)
test = Test()
session.add(test)
session.commit()
But this doen't work and i don`t know why.
I've got this error:
sqlalchemy.exc.OperationalError: (mariadb.OperationalError)
Unknown prepared statement handler (4294967295) given to mysqld_stmt_execute
[SQL: INSERT INTO test_table (name) VALUES (?)]
[parameters: (None,)]
I want to update the table with data of class Test.
I have used flask-sqlalchemy to create a mixin in a file called itemAbstract.py, to be shared by two model classes: ItemModel and ItemHistoryModelrespectively. Below is the code I have written in the itemAbstract.py
from databaseHandler import databaseHandler
from sqlalchemy.ext.declarative import declared_attr
# pylint: disable=maybe-no-member
class Item(databaseHandler.Model):
__abstract__ = True
itemName = databaseHandler.Column(databaseHandler.String(80), nullable = False)
price = databaseHandler.Column(databaseHandler.Numeric, nullable = False)
itemImage = databaseHandler.Column(databaseHandler.String(1000), nullable = False)
#classmethod
#declared_attr
def restaurantId(cls):
return databaseHandler.Column(
databaseHandler.Integer, databaseHandler.ForeignKey("restaurant.restaurantId"))
#classmethod
#declared_attr
def restaurant(cls):
return databaseHandler.relationship(
"RestaurantModel", backref=databaseHandler.backref('items', lazy=True))
#classmethod
#declared_attr
def productTypeId(cls):
return databaseHandler.Column(
databaseHandler.Integer, databaseHandler.ForeignKey("product_type.productTypeId"))
#classmethod
#declared_attr
def productType(cls):
return databaseHandler.relationship(
"ProductTypeModel", backref=databaseHandler.backref('items', lazy=True))
And I have inherited it in the itemModel.py and itemHistoryModel.py like so:
from databaseHandler import databaseHandler
from sqlalchemy import and_, or_
from abstracts.itemAbstract import Item
# pylint: disable=maybe-no-member
class ItemModel(Item):
__tablename__ = 'item'
itemId = databaseHandler.Column(databaseHandler.Integer, primary_key = True)
And
from databaseHandler import databaseHandler
from sqlalchemy import and_, or_
from abstracts.itemAbstract import Item
# pylint: disable=maybe-no-member
class ItemHistoryModel(Item):
__tablename__ = 'item_history'
historyId = databaseHandler.Column(databaseHandler.Integer, primary_key = True)
I have a class method in both files that is supposed to help me get a list of items a restaurant sells by passing in the restaurantId as parameter
#classmethod
def findItemsByRestaurant(cls, param):
return cls.query.filter_by(restaurantId = param)
However, anytime I execute this method it returns a query string in the resultset instead of a list of items. Here is a sample resultset:
SELECT item_history.`itemName` AS `item_history_itemName`, item_history.price AS item_history_price, item_history.`itemImage` AS `item_history_itemImage`, item_history.`historyId` AS `item_history_historyId`
FROM item_history
WHERE false = 1
Somehow, SQLAlchemy makes my parameter false and assigns a value of 1 to it meanwhile the actual ID of the restaurant is 10. What am I doing wrong?
This is the databaseHandler.py file:
from flask_sqlalchemy import SQLAlchemy
databaseHandler = SQLAlchemy()
The Query object has a number of API methods for getting pythonic objects rather than amending the query:
get
all
from_statement
first
one_or_none
one
scalar (as_scalar to be depreciated)
count
I'm attempting to add custom data types to Cerberus. The UUID class works as expected (it's a standard library class) but I'm not able to validate with the UUID type using Cerberus.
Secondarily I was not able to register multiple types in an an __init__ function but that probably should be it's own question.
Here's my custom validator that should register the given types.
import cerberus
class UUID:
name = "UUID"
def __init__(self, potential_uuid: str):
self.uuid = uuid.UUID(potential_uuid)
def __str__(self):
return str(self.uuid)
class Validator(cerberus.Validator):
def _register_types(self) -> cerberus.Validator.types_mapping:
types_mapping = Validator.types_mapping.copy()
for schema_type in datatypes.ALL_TYPES:
cerberus_type = cerberus.TypeDefinition(
schema_type.name,
(schema_type,),
())
types_mapping[schema_type.name] = cerberus_type
return types_mapping
cerberus_type = cerberus.TypeDefinition(
"UUID",
(datatypes.UUID,),
())
types_mapping = cerberus.Validator.types_mapping.copy()
types_mapping["UUID"] = cerberus_type
#def __init__(self, *args, **kwargs ):
# types_mapping = self._register_types()
# super().__init__(*args, **kwargs)
And here's my unit tests for this code.
#pytest.mark.unit
def test_valid_uuid():
test_input = "35d6d5a0-6f37-4794-a493-2712eda41c1a"
actual = UUID(test_input)
assert str(actual) == "35d6d5a0-6f37-4794-a493-2712eda41c1a"
#pytest.mark.unit
def test_invalid_uuid():
test_input = "Not a Valid UUID"
with pytest.raises(ValueError):
actual = UUID(test_input)
#pytest.mark.unit
def test_uuid_type_registration():
test_schema = {"test_name": {"type": "UUID"}}
validator = Validator(test_schema)
test_record = {"test_name": "35d6d5a0-6f37-4794-a493-2712eda41c1a"}
result = validator.validate(test_record)
print(validator._errors)
assert result == True
If we just give the UUID class a valid UUID it succeeds but if we attempt to validate it through Cerberus we get a BAD_TYPE validation error.
pytest tests/test_datatypes/test_datatypes.py
============================================================================================================================= test session starts ==============================================================================================================================
platform linux -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
benchmark: 3.2.2 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/vdev, inifile: setup.cfg
plugins: cov-2.6.1, benchmark-3.2.2
collected 4 items
tests/test_datatypes/test_datatypes.py ...F [100%]
=================================================================================================================================== FAILURES ===================================================================================================================================
_________________________________________________________________________________________________________________________ test_uuid_type_registration __________________________________________________________________________________________________________________________
#pytest.mark.unit
def test_uuid_type_registration():
test_schema = {"test_name": {"type": "UUID"}}
validator = Validator(test_schema)
test_record = {"test_name": "35d6d5a0-6f37-4794-a493-2712eda41c1a"}
result = validator.validate(test_record)
print(validator._errors)
> assert result == True
E assert False == True
tests/test_datatypes/test_datatypes.py:30: AssertionError
----------------------------------------------------------------------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------------------------------------------------------------------
[ValidationError # 0x7fa477e10278 ( document_path=('test_name',),schema_path=('test_name', 'type'),code=0x24,constraint="UUID",value="35d6d5a0-6f37-4794-a493-2712eda41c1a",info=() )]
=============================================================================================================================== warnings summary ===============================================================================================================================
/usr/local/lib/python3.7/site-packages/cerberus/validator.py:14
/usr/local/lib/python3.7/site-packages/cerberus/validator.py:14
/usr/local/lib/python3.7/site-packages/cerberus/validator.py:14
/usr/local/lib/python3.7/site-packages/cerberus/validator.py:14
/usr/local/lib/python3.7/site-packages/cerberus/validator.py:14: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
from collections import Hashable, Iterable, Mapping, Sequence
/usr/local/lib/python3.7/site-packages/cerberus/errors.py:6
/usr/local/lib/python3.7/site-packages/cerberus/errors.py:6: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
from collections import defaultdict, namedtuple, MutableMapping
/usr/local/lib/python3.7/site-packages/cerberus/schema.py:3
/usr/local/lib/python3.7/site-packages/cerberus/schema.py:3: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
from collections import (Callable, Hashable, Iterable, Mapping,
-- Docs: https://docs.pytest.org/en/latest/warnings.html
================================================================================================================ 1 failed, 3 passed, 6 warnings in 0.33 seconds ===============================================================================================================
EDIT 1
Simplified example code
import cerberus
import uuid
class Validator(cerberus.Validator):
types_mapping = {
**cerberus.Validator.types_mapping,
'UUID': cerberus.TypeDefinition('UUID', (uuid.UUID,), ())
}
Same failure
#pytest.mark.unit
def test_uuid_type_registration():
test_schema = {"test_name": {"type": "UUID"}}
validator = es_client.Validator(test_schema)
test_record = {"test_name": "35d6d5a0-6f37-4794-a493-2712eda41c1a"}
result = validator.validate(test_record)
print(validator._errors)
> assert result == True
E assert False == True
tests/test_datatypes/test_datatypes.py:30: AssertionError
------------------------------------------------------------ Captured stdout call -------------------------------------------------------------
[ValidationError # 0x7fd9cdeed0b8 ( document_path=('test_name',),schema_path=('test_name', 'type'),code=0x24,constraint="UUID",value="35d6d5a0-6f37-4794-a493-2712eda41c1a",info=() )]
Could you clarify what the _register_types method is meant to do and when it is called?
This works, maybe it helps you find your error:
def test_issue_475():
class UUID:
def __init__(self, data):
self.data = data
class MyValidator(Validator):
types_mapping = {
**Validator.types_mapping,
'UUID': TypeDefinition('UUID', (UUID,), ())
}
assert_success(
{'field': UUID(0)},
{'field': {'type': 'UUID'}},
validator=MyValidator()
)
Note that you mention the sdtlib's UUID class while you implement another one with the same name in your example.
I want to validate since the instance creation if the type is right or wrong,
i tried using #dataclass decorator but doesn't allow me to use the __init__ method, i also tried using a custom like class type
also in order of the type made some validations (if is a int, that field>0 or if is a str clean whitespaces, for example),
i could use a dict to validate the type, but i want to know if there's a way to do it in pythonic way
class Car(object):
""" My class with many fields """
color: str
name: str
wheels: int
def __init__(self):
""" Get the type of fields and validate """
pass
You can use the __post_init__ method of dataclasses to do your validations.
Below I just confirm that everything is an instance of the indicated type
from dataclasses import dataclass, fields
def validate(instance):
for field in fields(instance):
attr = getattr(instance, field.name)
if not isinstance(attr, field.type):
msg = "Field {0.name} is of type {1}, should be {0.type}".format(field, type(attr))
raise ValueError(msg)
#dataclass
class Car:
color: str
name: str
wheels: int
def __post_init__(self):
validate(self)
An alternative to #dataclass is to use pyfields. It provides validation and conversion out of the box, and is directly done at the field level so you can use fields inside any class, without modifying them in any way.
from pyfields import field, init_fields
from valid8.validation_lib import is_in
ALLOWED_COLORS = ('blue', 'yellow', 'brown')
class Car(object):
""" My class with many fields """
color: str = field(check_type=True, validators=is_in(ALLOWED_COLORS))
name: str = field(check_type=True, validators={'should be non-empty': lambda s: len(s) > 0})
wheels: int = field(check_type=True, validators={'should be positive': lambda x: x > 0})
#init_fields
def __init__(self, msg="hello world!"):
print(msg)
c = Car(color='blue', name='roadie', wheels=3)
c.wheels = 'hello' # <-- (1) type validation error, see below
c.wheels = 0 # <-- (2) value validation error, see below
yields the following two errors
TypeError: Invalid value type provided for '<...>.Car.wheels'.
Value should be of type <class 'int'>. Instead, received a 'str': 'hello'
and
valid8.entry_points.ValidationError[ValueError]:
Error validating [<...>.Car.wheels=0].
InvalidValue: should be positive.
Function [<lambda>] returned [False] for value 0.
See pyfields documentation for details. I'm the author by the way :)
Dataclasses do not check the data. But I made a small superstructure for dataclasses, and you can use it this way:
import json
from dataclasses import dataclass
from validated_dc import ValidatedDC
#dataclass
class Car(ValidatedDC):
color: str
name: str
wheels: int
# This string was received by api
data = '{"color": "gray", "name": "Delorean", "wheels": 4}'
# Let's upload this json-string to the dictionary
data = json.loads(data)
car = Car(**data)
assert car.get_errors() is None
# Let's say the key "color" got the wrong value:
data['color'] = 11111
car = Car(**data)
assert car.get_errors()
print(car.get_errors())
# {
# 'color': [
# BasicValidationError(
# value_repr='11111', value_type=<class 'int'>,
# annotation=<class 'str'>, exception=None
# )
# ]
# }
# fix
car.color = 'gray'
# is_valid() - Starting validation of an already created instance
# (if True returns, then there are no errors)
assert car.is_valid()
assert car.get_errors() is None
ValidatedDC: https://github.com/EvgeniyBurdin/validated_dc
Use pydantic.
In this example, the field password1 is only validated for being a string, while other fields have custom validator functions.
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
name: str
username: str
password1: str
password2: str
#validator('name')
def name_must_contain_space(cls, v):
if ' ' not in v:
raise ValueError('must contain a space')
return v.title()
#validator('password2')
def passwords_match(cls, v, values, **kwargs):
if 'password1' in values and v != values['password1']:
raise ValueError('passwords do not match')
return v
#validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
user = UserModel(
name='samuel colvin',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn',
)
print(user)
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
UserModel(
name='samuel',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn2',
)
except ValidationError as e:
print(e)
"""
2 validation errors for UserModel
name
must contain a space (type=value_error)
password2
passwords do not match (type=value_error)
"""
Starting SQLalchemy user here. I plan to use UUID's as the primary keys for my tables.
In the tutorial I saw some code for using the native Python UUID type in ORM classes. Eureka! I can use Postgresql's native UUID type for my system database and this TypeDecorator will stringify the UUID's for SQLite on my mobile clients.
http://docs.sqlalchemy.org/en/latest/core/types.html#backend-agnostic-guid-type
Sadness. When using this with an existing SQLite database that has stringified UUID's as the primary key I get stale data errors when I try to commit any changes.
This class crashes with stale data on commit.
class CommodityTypes(Base):
__tablename__ = 'CommodityTypes'
uuidKey = Column(GUID, primary_key=True)
myName = Column(String, unique = True)
sortKey = Column(Integer, unique = True)
, but this class works:
class NewTypes(Base):
__tablename__ = 'CommodityTypes'
uuidKey = Column(String, primary_key=True)
myName = Column(String, unique = True)
sortKey = Column(Integer, unique = True)
Queried objects from the CommodityTypes class show the python UUID type for uuidKey. The script queries the object correctly. I can change settings, but I can't commit. The decorated uuidKey doesn't seem to work.
I can go forward just using Strings for the uuidKey columns, but it frustrates me that the code from http://docs.sqlalchemy.org/en/latest/core/types.html#backend-agnostic-guid-type almost works.
Here's sample code with the problem. The string workaround not using the GUID type decorator is commented out.
#system modules
import uuid
#other modules
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy.types import TypeDecorator, CHAR
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
engine = create_engine('sqlite:////home/XXXX/XobfuscatedXXXX/XXXXXXXX.sqlite')
Base = declarative_base()
Session = sessionmaker(bind=engine)
class GUID(TypeDecorator):
"""Platform-independent GUID type.
Uses Postgresql's UUID type, otherwise uses
CHAR(32), storing as stringified hex values.
"""
impl = CHAR
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
return dialect.type_descriptor(UUID())
else:
return dialect.type_descriptor(CHAR(32))
def process_bind_param(self, value, dialect):
if value is None:
return value
elif dialect.name == 'postgresql':
return str(value)
else:
if not isinstance(value, uuid.UUID):
return "%.32x" % uuid.UUID(value)
else:
# hexstring
return "%.32x" % value
def process_result_value(self, value, dialect):
if value is None:
return value
else:
return uuid.UUID(value)
from sqlalchemy import Column, Boolean, DateTime, Date, Float, ForeignKey, Integer, Numeric, String
class CommodityTypes(Base):
__tablename__ = 'CommodityTypes'
uuidKey = Column(GUID, primary_key=True)
myName = Column(String, unique = True)
sortKey = Column(Integer, unique = True)
#class NewTypes(Base):
# __tablename__ = 'CommodityTypes'
# uuidKey = Column(String, primary_key=True)
# myName = Column(String, unique = True)
# sortKey = Column(Integer, unique = True)
if __name__=="__main__":
session = Session()
# newList = session.query(NewTypes).order_by(NewTypes.sortKey)
# for instance in newList:
# print(instance.myName)
#
# nt = newList[1]
# print(nt.myName)
# print(nt.sortKey)
# nt.sortKey = 11
# print(nt.sortKey)
# session.commit()
# print(nt.sortKey)
ctList = session.query(CommodityTypes).order_by(CommodityTypes.sortKey)
for instance in ctList:
print(instance.myName)
ct = ctList[1]
print(ct.myName)
print(ct.sortKey)
ct.sortKey = 22
print(ct.sortKey)
session.commit()
print(ct.sortKey)
Oh, forgot to mention software versions:
Python 3.1.3 (r313:86834, Dec 1 2010, 06:15:12)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2