WIP - Pydantic v2 support (#1238)
* WIP * WIP - make test_model_definition tests pass * WIP - make test_model_methods pass * WIP - make whole test suit at least run - failing 49/443 tests * WIP fix part of the getting pydantic tests as types of fields are now kept in core schema and not on fieldsinfo * WIP fix validation in update by creating individual fields validators, failing 36/443 * WIP fix __pydantic_extra__ in intializing model, fix test related to pydantic config checks, failing 32/442 * WIP - fix enum schema in model_json_schema, failing 31/442 * WIP - fix copying through model, fix setting pydantic fields on through, fix default config and inheriting from it, failing 26/442 * WIP fix tests checking pydantic schema, fix excluding parent fields, failing 21/442 * WIP some missed files * WIP - fix validators inheritance and fix validators in generated pydantic, failing 17/442 * WIP - fix through models setting - only on reverse side of relation, but always on reverse side, failing 15/442 * WIP - fix through models setting - only on reverse side of relation, but always on reverse side, failing 15/442 * WIP - working on proper populating __dict__ for relations for new schema dumping, some work on openapi docs, failing 13/442 * WIP - remove property fields as pydantic has now computed_field on its own, failing 9/442 * WIP - fixes in docs, failing 8/442 * WIP - fix tests for largebinary schema, wrapped bytes fields fail in pydantic, will be fixed in pydantic-core, remaining is circural schema for related models, failing 6/442 * WIP - fix to pk only models in schemas * Getting test suites to pass (#1249) * wip, fixing tests * iteration, fixing some more tests * iteration, fixing some more tests * adhere to comments * adhere to comments * remove unnecessary dict call, re-add getattribute for testing * todo for reverse relationship * adhere to comments, remove prints * solve circular refs * all tests pass 🎉 * remove 3.7 from tests * add lint and type check jobs * reforat with ruff, fix jobs * rename jobs * fix imports * fix evaluate in py3.8 * partially fix coverage * fix coverage, add more tests * fix test ids * fix test ids * fix lint, fix docs, make docs fully working scripts, add test docs job * fix pyproject * pin py ver in test docs * change dir in test docs * fix pydantic warning hack * rm poetry call in test_docs * switch to pathlib in test docs * remove coverage req test docs * fix type check tests, fix part of types * fix/skip next part of types * fix next part of types * fix next part of types * fix coverage * fix coverage * fix type (bit dirty 🤷) * fix some code smells * change pre-commit * tweak workflows * remove no root from tests * switch to full python path by passing sys.executable * some small refactor in new base model, one sample test, change makefile * small refactors to reduce complexity of methods * temp add tests for prs against pydantic_v2 * remove all references to __fields__ * remove all references to construct, deprecate the method and update model_construct to be in line with pydantic * deprecate dict and add model_dump, todo switch to model_dict in calls * fix tests * change to union * change to union * change to model_dump and model_dump_json from dict and json deprecated methods, deprecate them in ormar too * finish switching dict() -> model_dump() * finish switching json() -> model_dump_json() * remove fully pydantic_only * switch to extra for payment card, change missed json calls * fix coverage - no more warnings internal * fix coverage - no more warnings internal - part 2 * split model_construct into own and pydantic parts * split determine pydantic field type * change to new field validators * fix benchmarks, add codspeed instead of pytest-benchmark, add action and gh workflow * restore pytest-benchmark * remove codspeed * pin pydantic version, restore codspeed * change on push to pydantic_v2 to trigger first one * Use lifespan function instead of event (#1259) * check return types * fix imports order, set warnings=False on json that passes the dict, fix unnecessary loop in one of the test * remove references to model's meta as it's now ormar config, rename related methods too * filter out pydantic serializer warnings * remove choices leftovers * remove leftovers after property_fields, keep only enough to exclude them in initialization * add migration guide * fix meta references * downgrade databases for now * Change line numbers in documentation (#1265) * proofread and fix the docs, part 1 * proofread and fix the docs for models * proofread and fix the docs for fields * proofread and fix the docs for relations * proofread and fix rest of the docs, add release notes for 0.20 * create tables in new docs src * cleanup old deps, uncomment docs publish on tag * fix import reorder --------- Co-authored-by: TouwaStar <30479449+TouwaStar@users.noreply.github.com> Co-authored-by: Goran Mekić <meka@tilda.center>
This commit is contained in:
@ -1,49 +1,37 @@
|
||||
import datetime
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import pytest
|
||||
|
||||
metadata = sa.MetaData()
|
||||
db = databases.Database(DATABASE_URL)
|
||||
engine = create_engine(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "users"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="users")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
|
||||
|
||||
class RelationalAuditModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_by: User = ormar.ForeignKey(User, nullable=False)
|
||||
updated_by: User = ormar.ForeignKey(User, nullable=False)
|
||||
|
||||
|
||||
class AuditModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_by: str = ormar.String(max_length=100)
|
||||
updated_by: str = ormar.String(max_length=100, default="Sam")
|
||||
|
||||
|
||||
class DateFieldsModel(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_date: datetime.datetime = ormar.DateTime(
|
||||
default=datetime.datetime.now, name="creation_date"
|
||||
@ -54,9 +42,10 @@ class DateFieldsModel(ormar.Model):
|
||||
|
||||
|
||||
class Category(DateFieldsModel, AuditModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
exclude_parent_fields = ["updated_by", "updated_date"]
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="categories",
|
||||
exclude_parent_fields=["updated_by", "updated_date"],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -64,9 +53,10 @@ class Category(DateFieldsModel, AuditModel):
|
||||
|
||||
|
||||
class Item(DateFieldsModel, AuditModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "items"
|
||||
exclude_parent_fields = ["updated_by", "updated_date"]
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="items",
|
||||
exclude_parent_fields=["updated_by", "updated_date"],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -75,25 +65,22 @@ class Item(DateFieldsModel, AuditModel):
|
||||
|
||||
|
||||
class Gun(RelationalAuditModel, DateFieldsModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "guns"
|
||||
exclude_parent_fields = ["updated_by"]
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="guns",
|
||||
exclude_parent_fields=["updated_by"],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_model_definition():
|
||||
model_fields = Category.Meta.model_fields
|
||||
sqlalchemy_columns = Category.Meta.table.c
|
||||
pydantic_columns = Category.__fields__
|
||||
model_fields = Category.ormar_config.model_fields
|
||||
sqlalchemy_columns = Category.ormar_config.table.c
|
||||
pydantic_columns = Category.model_fields
|
||||
assert "updated_by" not in model_fields
|
||||
assert "updated_by" not in sqlalchemy_columns
|
||||
assert "updated_by" not in pydantic_columns
|
||||
@ -101,15 +88,15 @@ def test_model_definition():
|
||||
assert "updated_date" not in sqlalchemy_columns
|
||||
assert "updated_date" not in pydantic_columns
|
||||
|
||||
assert "updated_by" not in Gun.Meta.model_fields
|
||||
assert "updated_by" not in Gun.Meta.table.c
|
||||
assert "updated_by" not in Gun.__fields__
|
||||
assert "updated_by" not in Gun.ormar_config.model_fields
|
||||
assert "updated_by" not in Gun.ormar_config.table.c
|
||||
assert "updated_by" not in Gun.model_fields
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_works_as_expected():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
test = await Category(name="Cat", code=2, created_by="Joe").save()
|
||||
assert test.created_date is not None
|
||||
|
||||
@ -120,8 +107,8 @@ async def test_model_works_as_expected():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exclude_with_redefinition():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
test = await Item(name="Item", code=3, created_by="Anna").save()
|
||||
assert test.created_date is not None
|
||||
assert test.updated_by == "Bob"
|
||||
@ -133,8 +120,8 @@ async def test_exclude_with_redefinition():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exclude_with_relation():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
user = await User(name="Michail Kalasznikow").save()
|
||||
test = await Gun(name="AK47", created_by=user).save()
|
||||
assert test.created_date is not None
|
||||
|
||||
@ -1,26 +1,17 @@
|
||||
from typing import List, Optional
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import sqlalchemy
|
||||
from pydantic import ConstrainedStr
|
||||
from pydantic.typing import ForwardRef
|
||||
from typing import ForwardRef, List, Optional
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import pydantic
|
||||
from pydantic_core import PydanticUndefined
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class SelfRef(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "self_refs"
|
||||
ormar_config = base_ormar_config.copy(tablename="self_refs")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="selfref")
|
||||
@ -31,16 +22,14 @@ SelfRef.update_forward_refs()
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="test")
|
||||
@ -48,16 +37,14 @@ class Item(ormar.Model):
|
||||
|
||||
|
||||
class MutualA(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "mutual_a"
|
||||
ormar_config = base_ormar_config.copy(tablename="mutual_a")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
mutual_b = ormar.ForeignKey(ForwardRef("MutualB"), related_name="mutuals_a")
|
||||
|
||||
|
||||
class MutualB(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "mutual_b"
|
||||
ormar_config = base_ormar_config.copy(tablename="mutual_b")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name = ormar.String(max_length=100, default="test")
|
||||
@ -67,26 +54,50 @@ class MutualB(ormar.Model):
|
||||
MutualA.update_forward_refs()
|
||||
|
||||
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_getting_pydantic_model():
|
||||
PydanticCategory = Category.get_pydantic()
|
||||
assert issubclass(PydanticCategory, pydantic.BaseModel)
|
||||
assert {*PydanticCategory.__fields__.keys()} == {"items", "id", "name"}
|
||||
assert {*PydanticCategory.model_fields.keys()} == {"items", "id", "name"}
|
||||
|
||||
assert not PydanticCategory.__fields__["id"].required
|
||||
assert PydanticCategory.__fields__["id"].outer_type_ == int
|
||||
assert PydanticCategory.__fields__["id"].default is None
|
||||
assert not PydanticCategory.model_fields["id"].is_required()
|
||||
assert (
|
||||
PydanticCategory.__pydantic_core_schema__["schema"]["fields"]["id"]["schema"][
|
||||
"schema"
|
||||
]["schema"]["type"]
|
||||
== "int"
|
||||
)
|
||||
assert PydanticCategory.model_fields["id"].default is None
|
||||
|
||||
assert PydanticCategory.__fields__["name"].required
|
||||
assert issubclass(PydanticCategory.__fields__["name"].outer_type_, ConstrainedStr)
|
||||
assert PydanticCategory.__fields__["name"].default in [None, Ellipsis]
|
||||
assert PydanticCategory.model_fields["name"].is_required()
|
||||
assert (
|
||||
PydanticCategory.__pydantic_core_schema__["schema"]["fields"]["name"]["schema"][
|
||||
"type"
|
||||
]
|
||||
== "str"
|
||||
)
|
||||
assert PydanticCategory.model_fields["name"].default == PydanticUndefined
|
||||
|
||||
PydanticItem = PydanticCategory.__fields__["items"].type_
|
||||
assert PydanticCategory.__fields__["items"].outer_type_ == List[PydanticItem]
|
||||
PydanticItem = PydanticCategory.__pydantic_core_schema__["schema"]["fields"][
|
||||
"items"
|
||||
]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
assert (
|
||||
PydanticCategory.__pydantic_core_schema__["schema"]["fields"]["items"][
|
||||
"schema"
|
||||
]["schema"]["schema"]["type"]
|
||||
== "list"
|
||||
)
|
||||
assert (
|
||||
PydanticCategory.model_fields["items"].annotation
|
||||
== Optional[List[PydanticItem]]
|
||||
)
|
||||
assert issubclass(PydanticItem, pydantic.BaseModel)
|
||||
assert not PydanticItem.__fields__["name"].required
|
||||
assert PydanticItem.__fields__["name"].default == "test"
|
||||
assert issubclass(PydanticItem.__fields__["name"].outer_type_, ConstrainedStr)
|
||||
assert "category" not in PydanticItem.__fields__
|
||||
assert not PydanticItem.model_fields["name"].is_required()
|
||||
assert PydanticItem.model_fields["name"].default == "test"
|
||||
assert PydanticItem.model_fields["name"].annotation == Optional[str]
|
||||
assert "category" not in PydanticItem.model_fields
|
||||
|
||||
|
||||
def test_initializing_pydantic_model():
|
||||
@ -96,140 +107,199 @@ def test_initializing_pydantic_model():
|
||||
"items": [{"id": 1, "name": "test_i1"}, {"id": 2, "name": "test_i2"}],
|
||||
}
|
||||
PydanticCategory = Category.get_pydantic()
|
||||
ormar_cat = Category(**data)
|
||||
assert ormar_cat.model_dump() == data
|
||||
cat = PydanticCategory(**data)
|
||||
assert cat.dict() == data
|
||||
assert cat.model_dump() == data
|
||||
|
||||
data = {"id": 1, "name": "test"}
|
||||
cat = PydanticCategory(**data)
|
||||
assert cat.dict() == {**data, "items": None}
|
||||
assert cat.model_dump() == {**data, "items": None}
|
||||
|
||||
|
||||
def test_getting_pydantic_model_include():
|
||||
PydanticCategory = Category.get_pydantic(include={"id", "name"})
|
||||
assert len(PydanticCategory.__fields__) == 2
|
||||
assert "items" not in PydanticCategory.__fields__
|
||||
assert len(PydanticCategory.model_fields) == 2
|
||||
assert "items" not in PydanticCategory.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_nested_include_set():
|
||||
PydanticCategory = Category.get_pydantic(include={"id", "items__id"})
|
||||
assert len(PydanticCategory.__fields__) == 2
|
||||
assert "name" not in PydanticCategory.__fields__
|
||||
PydanticItem = PydanticCategory.__fields__["items"].type_
|
||||
assert len(PydanticItem.__fields__) == 1
|
||||
assert "id" in PydanticItem.__fields__
|
||||
assert len(PydanticCategory.model_fields) == 2
|
||||
assert "name" not in PydanticCategory.model_fields
|
||||
PydanticItem = PydanticCategory.__pydantic_core_schema__["schema"]["fields"][
|
||||
"items"
|
||||
]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
assert len(PydanticItem.model_fields) == 1
|
||||
assert "id" in PydanticItem.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_nested_include_dict():
|
||||
PydanticCategory = Category.get_pydantic(include={"id": ..., "items": {"id"}})
|
||||
assert len(PydanticCategory.__fields__) == 2
|
||||
assert "name" not in PydanticCategory.__fields__
|
||||
PydanticItem = PydanticCategory.__fields__["items"].type_
|
||||
assert len(PydanticItem.__fields__) == 1
|
||||
assert "id" in PydanticItem.__fields__
|
||||
assert len(PydanticCategory.model_fields) == 2
|
||||
assert "name" not in PydanticCategory.model_fields
|
||||
PydanticItem = PydanticCategory.__pydantic_core_schema__["schema"]["fields"][
|
||||
"items"
|
||||
]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
assert len(PydanticItem.model_fields) == 1
|
||||
assert "id" in PydanticItem.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_nested_include_nested_dict():
|
||||
PydanticCategory = Category.get_pydantic(include={"id": ..., "items": {"id": ...}})
|
||||
assert len(PydanticCategory.__fields__) == 2
|
||||
assert "name" not in PydanticCategory.__fields__
|
||||
PydanticItem = PydanticCategory.__fields__["items"].type_
|
||||
assert len(PydanticItem.__fields__) == 1
|
||||
assert "id" in PydanticItem.__fields__
|
||||
assert len(PydanticCategory.model_fields) == 2
|
||||
assert "name" not in PydanticCategory.model_fields
|
||||
PydanticItem = PydanticCategory.__pydantic_core_schema__["schema"]["fields"][
|
||||
"items"
|
||||
]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
assert len(PydanticItem.model_fields) == 1
|
||||
assert "id" in PydanticItem.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_include_exclude():
|
||||
PydanticCategory = Category.get_pydantic(
|
||||
include={"id": ..., "items": {"id", "name"}}, exclude={"items__name"}
|
||||
)
|
||||
assert len(PydanticCategory.__fields__) == 2
|
||||
assert "name" not in PydanticCategory.__fields__
|
||||
PydanticItem = PydanticCategory.__fields__["items"].type_
|
||||
assert len(PydanticItem.__fields__) == 1
|
||||
assert "id" in PydanticItem.__fields__
|
||||
assert len(PydanticCategory.model_fields) == 2
|
||||
assert "name" not in PydanticCategory.model_fields
|
||||
PydanticItem = PydanticCategory.__pydantic_core_schema__["schema"]["fields"][
|
||||
"items"
|
||||
]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
assert len(PydanticItem.model_fields) == 1
|
||||
assert "id" in PydanticItem.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_exclude():
|
||||
PydanticItem = Item.get_pydantic(exclude={"category__name"})
|
||||
assert len(PydanticItem.__fields__) == 3
|
||||
assert "category" in PydanticItem.__fields__
|
||||
PydanticCategory = PydanticItem.__fields__["category"].type_
|
||||
assert len(PydanticCategory.__fields__) == 1
|
||||
assert "name" not in PydanticCategory.__fields__
|
||||
assert len(PydanticItem.model_fields) == 3
|
||||
assert "category" in PydanticItem.model_fields
|
||||
PydanticCategory = PydanticItem.__pydantic_core_schema__["schema"]["fields"][
|
||||
"category"
|
||||
]["schema"]["schema"]["schema"]["cls"]
|
||||
assert len(PydanticCategory.model_fields) == 1
|
||||
assert "name" not in PydanticCategory.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_exclude_dict():
|
||||
PydanticItem = Item.get_pydantic(exclude={"id": ..., "category": {"name"}})
|
||||
assert len(PydanticItem.__fields__) == 2
|
||||
assert "category" in PydanticItem.__fields__
|
||||
assert "id" not in PydanticItem.__fields__
|
||||
PydanticCategory = PydanticItem.__fields__["category"].type_
|
||||
assert len(PydanticCategory.__fields__) == 1
|
||||
assert "name" not in PydanticCategory.__fields__
|
||||
assert len(PydanticItem.model_fields) == 2
|
||||
assert "category" in PydanticItem.model_fields
|
||||
assert "id" not in PydanticItem.model_fields
|
||||
PydanticCategory = PydanticItem.__pydantic_core_schema__["schema"]["fields"][
|
||||
"category"
|
||||
]["schema"]["schema"]["schema"]["cls"]
|
||||
assert len(PydanticCategory.model_fields) == 1
|
||||
assert "name" not in PydanticCategory.model_fields
|
||||
|
||||
|
||||
def test_getting_pydantic_model_self_ref():
|
||||
PydanticSelfRef = SelfRef.get_pydantic()
|
||||
assert len(PydanticSelfRef.__fields__) == 4
|
||||
assert set(PydanticSelfRef.__fields__.keys()) == {
|
||||
assert len(PydanticSelfRef.model_fields) == 4
|
||||
assert set(PydanticSelfRef.model_fields.keys()) == {
|
||||
"id",
|
||||
"name",
|
||||
"parent",
|
||||
"children",
|
||||
}
|
||||
InnerSelf = PydanticSelfRef.__fields__["parent"].type_
|
||||
assert len(InnerSelf.__fields__) == 2
|
||||
assert set(InnerSelf.__fields__.keys()) == {"id", "name"}
|
||||
inner_self_ref_id = PydanticSelfRef.__pydantic_core_schema__["schema"]["schema"][
|
||||
"fields"
|
||||
]["parent"]["schema"]["schema"]["schema"]["schema_ref"]
|
||||
InnerSelf = next(
|
||||
(
|
||||
x
|
||||
for x in PydanticSelfRef.__pydantic_core_schema__["definitions"]
|
||||
if x["ref"] == inner_self_ref_id
|
||||
)
|
||||
)["cls"]
|
||||
assert len(InnerSelf.model_fields) == 2
|
||||
assert set(InnerSelf.model_fields.keys()) == {"id", "name"}
|
||||
|
||||
InnerSelf2 = PydanticSelfRef.__fields__["children"].type_
|
||||
assert len(InnerSelf2.__fields__) == 2
|
||||
assert set(InnerSelf2.__fields__.keys()) == {"id", "name"}
|
||||
inner_self_ref_id2 = PydanticSelfRef.__pydantic_core_schema__["schema"]["schema"][
|
||||
"fields"
|
||||
]["children"]["schema"]["schema"]["schema"]["items_schema"]["schema_ref"]
|
||||
InnerSelf2 = next(
|
||||
(
|
||||
x
|
||||
for x in PydanticSelfRef.__pydantic_core_schema__["definitions"]
|
||||
if x["ref"] == inner_self_ref_id2
|
||||
)
|
||||
)["cls"]
|
||||
assert len(InnerSelf2.model_fields) == 2
|
||||
assert set(InnerSelf2.model_fields.keys()) == {"id", "name"}
|
||||
|
||||
|
||||
def test_getting_pydantic_model_self_ref_exclude():
|
||||
PydanticSelfRef = SelfRef.get_pydantic(exclude={"children": {"name"}})
|
||||
assert len(PydanticSelfRef.__fields__) == 4
|
||||
assert set(PydanticSelfRef.__fields__.keys()) == {
|
||||
assert len(PydanticSelfRef.model_fields) == 4
|
||||
assert set(PydanticSelfRef.model_fields.keys()) == {
|
||||
"id",
|
||||
"name",
|
||||
"parent",
|
||||
"children",
|
||||
}
|
||||
|
||||
InnerSelf = PydanticSelfRef.__fields__["parent"].type_
|
||||
assert len(InnerSelf.__fields__) == 2
|
||||
assert set(InnerSelf.__fields__.keys()) == {"id", "name"}
|
||||
InnerSelf = PydanticSelfRef.__pydantic_core_schema__["schema"]["fields"]["parent"][
|
||||
"schema"
|
||||
]["schema"]["schema"]["cls"]
|
||||
assert len(InnerSelf.model_fields) == 2
|
||||
assert set(InnerSelf.model_fields.keys()) == {"id", "name"}
|
||||
|
||||
PydanticSelfRefChildren = PydanticSelfRef.__fields__["children"].type_
|
||||
assert len(PydanticSelfRefChildren.__fields__) == 1
|
||||
assert set(PydanticSelfRefChildren.__fields__.keys()) == {"id"}
|
||||
# PydanticSelfRefChildren = PydanticSelfRef.model_fields["children"].type_
|
||||
PydanticSelfRefChildren = PydanticSelfRef.__pydantic_core_schema__["schema"][
|
||||
"fields"
|
||||
]["children"]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
assert len(PydanticSelfRefChildren.model_fields) == 1
|
||||
assert set(PydanticSelfRefChildren.model_fields.keys()) == {"id"}
|
||||
assert PydanticSelfRef != PydanticSelfRefChildren
|
||||
assert InnerSelf != PydanticSelfRefChildren
|
||||
|
||||
|
||||
def test_getting_pydantic_model_mutual_rels():
|
||||
MutualAPydantic = MutualA.get_pydantic()
|
||||
assert len(MutualAPydantic.__fields__) == 3
|
||||
assert set(MutualAPydantic.__fields__.keys()) == {"id", "mutual_b", "mutuals_b"}
|
||||
assert len(MutualAPydantic.model_fields) == 3
|
||||
assert set(MutualAPydantic.model_fields.keys()) == {"id", "mutual_b", "mutuals_b"}
|
||||
|
||||
MutualB1 = MutualAPydantic.__fields__["mutual_b"].type_
|
||||
MutualB2 = MutualAPydantic.__fields__["mutuals_b"].type_
|
||||
assert len(MutualB1.__fields__) == 2
|
||||
assert set(MutualB1.__fields__.keys()) == {"id", "name"}
|
||||
assert len(MutualB2.__fields__) == 2
|
||||
assert set(MutualB2.__fields__.keys()) == {"id", "name"}
|
||||
mutual_ref_1 = MutualAPydantic.__pydantic_core_schema__["schema"]["schema"][
|
||||
"fields"
|
||||
]["mutual_b"]["schema"]["schema"]["schema"]["schema_ref"]
|
||||
MutualB1 = next(
|
||||
(
|
||||
x
|
||||
for x in MutualAPydantic.__pydantic_core_schema__["definitions"]
|
||||
if x["ref"] == mutual_ref_1
|
||||
)
|
||||
)["cls"]
|
||||
mutual_ref_2 = MutualAPydantic.__pydantic_core_schema__["schema"]["schema"][
|
||||
"fields"
|
||||
]["mutuals_b"]["schema"]["schema"]["schema"]["items_schema"]["schema_ref"]
|
||||
MutualB2 = next(
|
||||
(
|
||||
x
|
||||
for x in MutualAPydantic.__pydantic_core_schema__["definitions"]
|
||||
if x["ref"] == mutual_ref_2
|
||||
)
|
||||
)["cls"]
|
||||
assert len(MutualB1.model_fields) == 2
|
||||
assert set(MutualB1.model_fields.keys()) == {"id", "name"}
|
||||
assert len(MutualB2.model_fields) == 2
|
||||
assert set(MutualB2.model_fields.keys()) == {"id", "name"}
|
||||
assert MutualB1 == MutualB2
|
||||
|
||||
|
||||
def test_getting_pydantic_model_mutual_rels_exclude():
|
||||
MutualAPydantic = MutualA.get_pydantic(exclude={"mutual_b": {"name"}})
|
||||
assert len(MutualAPydantic.__fields__) == 3
|
||||
assert set(MutualAPydantic.__fields__.keys()) == {"id", "mutual_b", "mutuals_b"}
|
||||
assert len(MutualAPydantic.model_fields) == 3
|
||||
assert set(MutualAPydantic.model_fields.keys()) == {"id", "mutual_b", "mutuals_b"}
|
||||
|
||||
MutualB1 = MutualAPydantic.__fields__["mutual_b"].type_
|
||||
MutualB2 = MutualAPydantic.__fields__["mutuals_b"].type_
|
||||
MutualB1 = MutualAPydantic.__pydantic_core_schema__["schema"]["fields"]["mutual_b"][
|
||||
"schema"
|
||||
]["schema"]["schema"]["cls"]
|
||||
MutualB2 = MutualAPydantic.__pydantic_core_schema__["schema"]["fields"][
|
||||
"mutuals_b"
|
||||
]["schema"]["schema"]["schema"]["items_schema"]["cls"]
|
||||
|
||||
assert len(MutualB1.__fields__) == 1
|
||||
assert set(MutualB1.__fields__.keys()) == {"id"}
|
||||
assert len(MutualB2.__fields__) == 2
|
||||
assert set(MutualB2.__fields__.keys()) == {"id", "name"}
|
||||
assert len(MutualB1.model_fields) == 1
|
||||
assert set(MutualB1.model_fields.keys()) == {"id"}
|
||||
assert len(MutualB2.model_fields) == 2
|
||||
assert set(MutualB2.model_fields.keys()) == {"id", "name"}
|
||||
assert MutualB1 != MutualB2
|
||||
|
||||
@ -1,42 +1,37 @@
|
||||
# type: ignore
|
||||
import datetime
|
||||
from typing import List, Optional
|
||||
from collections import Counter
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import create_engine
|
||||
from typing import Optional
|
||||
|
||||
import ormar
|
||||
import ormar.fields.constraints
|
||||
from ormar import ModelDefinitionError, property_field
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from ormar import ModelDefinitionError
|
||||
from ormar.exceptions import ModelError
|
||||
from ormar.models.metaclass import get_constraint_copy
|
||||
from tests.settings import DATABASE_URL
|
||||
from ormar.relations.relation_proxy import RelationProxy
|
||||
from pydantic import computed_field
|
||||
|
||||
metadata = sa.MetaData()
|
||||
db = databases.Database(DATABASE_URL)
|
||||
engine = create_engine(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class AuditModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_by: str = ormar.String(max_length=100)
|
||||
updated_by: str = ormar.String(max_length=100, default="Sam")
|
||||
|
||||
@property_field
|
||||
def audit(self): # pragma: no cover
|
||||
@computed_field
|
||||
def audit(self) -> str: # pragma: no cover
|
||||
return f"{self.created_by} {self.updated_by}"
|
||||
|
||||
|
||||
class DateFieldsModelNoSubclass(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "test_date_models"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="test_date_models")
|
||||
|
||||
date_id: int = ormar.Integer(primary_key=True)
|
||||
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
@ -44,11 +39,9 @@ class DateFieldsModelNoSubclass(ormar.Model):
|
||||
|
||||
|
||||
class DateFieldsModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
constraints = [
|
||||
ormar_config = base_ormar_config.copy(
|
||||
abstract=True,
|
||||
constraints=[
|
||||
ormar.fields.constraints.UniqueColumns(
|
||||
"creation_date",
|
||||
"modification_date",
|
||||
@ -56,7 +49,8 @@ class DateFieldsModel(ormar.Model):
|
||||
ormar.fields.constraints.CheckColumns(
|
||||
"creation_date <= modification_date",
|
||||
),
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
created_date: datetime.datetime = ormar.DateTime(
|
||||
default=datetime.datetime.now, name="creation_date"
|
||||
@ -67,26 +61,26 @@ class DateFieldsModel(ormar.Model):
|
||||
|
||||
|
||||
class Category(DateFieldsModel, AuditModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
constraints = [ormar.fields.constraints.UniqueColumns("name", "code")]
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="categories",
|
||||
constraints=[ormar.fields.constraints.UniqueColumns("name", "code")],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
code: int = ormar.Integer()
|
||||
|
||||
@property_field
|
||||
def code_name(self):
|
||||
@computed_field
|
||||
def code_name(self) -> str:
|
||||
return f"{self.code}:{self.name}"
|
||||
|
||||
@property_field
|
||||
def audit(self):
|
||||
@computed_field
|
||||
def audit(self) -> str:
|
||||
return f"{self.created_by} {self.updated_by}"
|
||||
|
||||
|
||||
class Subject(DateFieldsModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -94,19 +88,14 @@ class Subject(DateFieldsModel):
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50)
|
||||
@ -116,135 +105,109 @@ class Car(ormar.Model):
|
||||
|
||||
|
||||
class Truck(Car):
|
||||
class Meta:
|
||||
pass
|
||||
ormar_config = ormar.OrmarConfig()
|
||||
|
||||
max_capacity: int = ormar.Integer()
|
||||
|
||||
|
||||
class Bus(Car):
|
||||
class Meta:
|
||||
tablename = "buses"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="buses")
|
||||
|
||||
owner: Person = ormar.ForeignKey(Person, related_name="buses")
|
||||
max_persons: int = ormar.Integer()
|
||||
|
||||
|
||||
class Car2(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50)
|
||||
owner: Person = ormar.ForeignKey(Person, related_name="owned")
|
||||
co_owners: List[Person] = ormar.ManyToMany(Person, related_name="coowned")
|
||||
co_owners: RelationProxy[Person] = ormar.ManyToMany(Person, related_name="coowned")
|
||||
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
|
||||
|
||||
class Truck2(Car2):
|
||||
class Meta:
|
||||
tablename = "trucks2"
|
||||
ormar_config = base_ormar_config.copy(tablename="trucks2")
|
||||
|
||||
max_capacity: int = ormar.Integer()
|
||||
|
||||
|
||||
class Bus2(Car2):
|
||||
class Meta:
|
||||
tablename = "buses2"
|
||||
ormar_config = base_ormar_config.copy(tablename="buses2")
|
||||
|
||||
max_persons: int = ormar.Integer()
|
||||
|
||||
|
||||
class ImmutablePerson(Person):
|
||||
class Config:
|
||||
allow_mutation = False
|
||||
validate_assignment = False
|
||||
model_config = dict(frozen=True, validate_assignment=False)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_init_of_abstract_model():
|
||||
def test_init_of_abstract_model() -> None:
|
||||
with pytest.raises(ModelError):
|
||||
DateFieldsModel()
|
||||
|
||||
|
||||
def test_duplicated_related_name_on_different_model():
|
||||
def test_duplicated_related_name_on_different_model() -> None:
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class Bus3(Car2): # pragma: no cover
|
||||
class Meta:
|
||||
tablename = "buses3"
|
||||
ormar_config = ormar.OrmarConfig(tablename="buses3")
|
||||
|
||||
owner: Person = ormar.ForeignKey(Person, related_name="buses")
|
||||
max_persons: int = ormar.Integer()
|
||||
|
||||
|
||||
def test_config_is_not_a_class_raises_error():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class ImmutablePerson2(Person):
|
||||
Config = dict(allow_mutation=False, validate_assignment=False)
|
||||
|
||||
|
||||
def test_field_redefining_in_concrete_models():
|
||||
def test_field_redefining_in_concrete_models() -> None:
|
||||
class RedefinedField(DateFieldsModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "redefines"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="redefines")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
created_date: str = ormar.String(max_length=200, name="creation_date")
|
||||
created_date: str = ormar.String(
|
||||
max_length=200,
|
||||
name="creation_date",
|
||||
) # type: ignore
|
||||
|
||||
changed_field = RedefinedField.Meta.model_fields["created_date"]
|
||||
changed_field = RedefinedField.ormar_config.model_fields["created_date"]
|
||||
assert changed_field.ormar_default is None
|
||||
assert changed_field.get_alias() == "creation_date"
|
||||
assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns)
|
||||
assert any(
|
||||
x.name == "creation_date" for x in RedefinedField.ormar_config.table.columns
|
||||
)
|
||||
assert isinstance(
|
||||
RedefinedField.Meta.table.columns["creation_date"].type, sa.sql.sqltypes.String
|
||||
RedefinedField.ormar_config.table.columns["creation_date"].type,
|
||||
sa.sql.sqltypes.String,
|
||||
)
|
||||
|
||||
|
||||
def test_model_subclassing_that_redefines_constraints_column_names():
|
||||
def test_model_subclassing_that_redefines_constraints_column_names() -> None:
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class WrongField2(DateFieldsModel): # pragma: no cover
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "wrongs"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="wrongs")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
created_date: str = ormar.String(max_length=200)
|
||||
created_date: str = ormar.String(max_length=200) # type: ignore
|
||||
|
||||
|
||||
def test_model_subclassing_non_abstract_raises_error():
|
||||
def test_model_subclassing_non_abstract_raises_error() -> None:
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class WrongField2(DateFieldsModelNoSubclass): # pragma: no cover
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "wrongs"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="wrongs")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
|
||||
|
||||
def test_params_are_inherited():
|
||||
assert Category.Meta.metadata == metadata
|
||||
assert Category.Meta.database == db
|
||||
assert len(Category.Meta.property_fields) == 2
|
||||
def test_params_are_inherited() -> None:
|
||||
assert Category.ormar_config.metadata == base_ormar_config.metadata
|
||||
assert Category.ormar_config.database == base_ormar_config.database
|
||||
assert len(Category.ormar_config.property_fields) == 2
|
||||
|
||||
constraints = Counter(map(lambda c: type(c), Category.Meta.constraints))
|
||||
constraints = Counter(map(lambda c: type(c), Category.ormar_config.constraints))
|
||||
assert constraints[ormar.fields.constraints.UniqueColumns] == 2
|
||||
assert constraints[ormar.fields.constraints.IndexColumns] == 0
|
||||
assert constraints[ormar.fields.constraints.CheckColumns] == 1
|
||||
@ -259,9 +222,9 @@ def round_date_to_seconds(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fields_inherited_from_mixin():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async def test_fields_inherited_from_mixin() -> None:
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
cat = await Category(
|
||||
name="Foo", code=123, created_by="Sam", updated_by="Max"
|
||||
).save()
|
||||
@ -269,19 +232,28 @@ async def test_fields_inherited_from_mixin():
|
||||
mixin_columns = ["created_date", "updated_date"]
|
||||
mixin_db_columns = ["creation_date", "modification_date"]
|
||||
mixin2_columns = ["created_by", "updated_by"]
|
||||
assert all(field in Category.Meta.model_fields for field in mixin_columns)
|
||||
assert all(
|
||||
field in Category.ormar_config.model_fields for field in mixin_columns
|
||||
)
|
||||
assert cat.created_date is not None
|
||||
assert cat.updated_date is not None
|
||||
assert all(field in Subject.Meta.model_fields for field in mixin_columns)
|
||||
assert all(
|
||||
field in Subject.ormar_config.model_fields for field in mixin_columns
|
||||
)
|
||||
assert cat.code_name == "123:Foo"
|
||||
assert cat.audit == "Sam Max"
|
||||
assert sub.created_date is not None
|
||||
assert sub.updated_date is not None
|
||||
|
||||
assert all(field in Category.Meta.model_fields for field in mixin2_columns)
|
||||
assert all(
|
||||
field not in Subject.Meta.model_fields for field in mixin2_columns
|
||||
field in Category.ormar_config.model_fields for field in mixin2_columns
|
||||
)
|
||||
assert all(
|
||||
field not in Subject.ormar_config.model_fields
|
||||
for field in mixin2_columns
|
||||
)
|
||||
|
||||
inspector = sa.inspect(engine)
|
||||
inspector = sa.inspect(base_ormar_config.engine)
|
||||
assert "categories" in inspector.get_table_names()
|
||||
table_columns = [x.get("name") for x in inspector.get_columns("categories")]
|
||||
assert all(
|
||||
@ -301,6 +273,7 @@ async def test_fields_inherited_from_mixin():
|
||||
assert round_date_to_seconds(sub2.created_date) == round_date_to_seconds(
|
||||
sub.created_date
|
||||
)
|
||||
assert sub2.category is not None
|
||||
assert sub2.category.updated_date is not None
|
||||
assert round_date_to_seconds(
|
||||
sub2.category.created_date
|
||||
@ -328,9 +301,9 @@ async def test_fields_inherited_from_mixin():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inheritance_with_relation():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async def test_inheritance_with_relation() -> None:
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
sam = await Person(name="Sam").save()
|
||||
joe = await Person(name="Joe").save()
|
||||
await Truck(
|
||||
@ -377,9 +350,9 @@ async def test_inheritance_with_relation():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inheritance_with_multi_relation():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async def test_inheritance_with_multi_relation() -> None:
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
sam = await Person(name="Sam").save()
|
||||
joe = await Person(name="Joe").save()
|
||||
alex = await Person(name="Alex").save()
|
||||
@ -523,16 +496,16 @@ async def test_inheritance_with_multi_relation():
|
||||
assert len(unicorns[1].co_owners) == 1
|
||||
|
||||
|
||||
def test_custom_config():
|
||||
def test_custom_config() -> None:
|
||||
# Custom config inherits defaults
|
||||
assert getattr(ImmutablePerson.__config__, "orm_mode") is True
|
||||
assert ImmutablePerson.model_config["from_attributes"] is True
|
||||
# Custom config can override defaults
|
||||
assert getattr(ImmutablePerson.__config__, "validate_assignment") is False
|
||||
assert ImmutablePerson.model_config["validate_assignment"] is False
|
||||
sam = ImmutablePerson(name="Sam")
|
||||
with pytest.raises(TypeError):
|
||||
with pytest.raises(pydantic.ValidationError):
|
||||
sam.name = "Not Sam"
|
||||
|
||||
|
||||
def test_get_constraint_copy():
|
||||
def test_get_constraint_copy() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
get_constraint_copy("INVALID CONSTRAINT")
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
# type: ignore
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
metadata = sa.MetaData()
|
||||
db = databases.Database(DATABASE_URL)
|
||||
engine = create_engine(DATABASE_URL)
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class AuditMixin:
|
||||
@ -26,10 +22,7 @@ class DateFieldsMixins:
|
||||
|
||||
|
||||
class Category(ormar.Model, DateFieldsMixins, AuditMixin):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -37,65 +30,59 @@ class Category(ormar.Model, DateFieldsMixins, AuditMixin):
|
||||
|
||||
|
||||
class Subject(ormar.Model, DateFieldsMixins):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "subjects"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="subjects")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
category: Optional[Category] = ormar.ForeignKey(Category)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_field_redefining():
|
||||
def test_field_redefining() -> None:
|
||||
class RedefinedField(ormar.Model, DateFieldsMixins):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "redefined"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="redefined")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
created_date: datetime.datetime = ormar.DateTime(name="creation_date")
|
||||
|
||||
assert RedefinedField.Meta.model_fields["created_date"].ormar_default is None
|
||||
assert (
|
||||
RedefinedField.Meta.model_fields["created_date"].get_alias() == "creation_date"
|
||||
RedefinedField.ormar_config.model_fields["created_date"].ormar_default is None
|
||||
)
|
||||
assert (
|
||||
RedefinedField.ormar_config.model_fields["created_date"].get_alias()
|
||||
== "creation_date"
|
||||
)
|
||||
assert any(
|
||||
x.name == "creation_date" for x in RedefinedField.ormar_config.table.columns
|
||||
)
|
||||
assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns)
|
||||
|
||||
|
||||
def test_field_redefining_in_second_raises_error():
|
||||
class OkField(ormar.Model, DateFieldsMixins): # pragma: no cover
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "oks"
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
def test_field_redefining_in_second() -> None:
|
||||
|
||||
class RedefinedField2(ormar.Model, DateFieldsMixins):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "redefines2"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="redefines2")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
created_date: str = ormar.String(max_length=200, name="creation_date")
|
||||
created_date: str = ormar.String(
|
||||
max_length=200,
|
||||
name="creation_date",
|
||||
) # type: ignore
|
||||
|
||||
assert RedefinedField2.Meta.model_fields["created_date"].ormar_default is None
|
||||
assert (
|
||||
RedefinedField2.Meta.model_fields["created_date"].get_alias() == "creation_date"
|
||||
RedefinedField2.ormar_config.model_fields["created_date"].ormar_default is None
|
||||
)
|
||||
assert (
|
||||
RedefinedField2.ormar_config.model_fields["created_date"].get_alias()
|
||||
== "creation_date"
|
||||
)
|
||||
assert any(
|
||||
x.name == "creation_date" for x in RedefinedField2.ormar_config.table.columns
|
||||
)
|
||||
assert any(x.name == "creation_date" for x in RedefinedField2.Meta.table.columns)
|
||||
assert isinstance(
|
||||
RedefinedField2.Meta.table.columns["creation_date"].type, sa.sql.sqltypes.String
|
||||
RedefinedField2.ormar_config.table.columns["creation_date"].type,
|
||||
sa.sql.sqltypes.String,
|
||||
)
|
||||
|
||||
|
||||
@ -108,28 +95,35 @@ def round_date_to_seconds(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fields_inherited_from_mixin():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
async def test_fields_inherited_from_mixin() -> None:
|
||||
async with base_ormar_config.database:
|
||||
async with base_ormar_config.database.transaction(force_rollback=True):
|
||||
cat = await Category(
|
||||
name="Foo", code=123, created_by="Sam", updated_by="Max"
|
||||
).save()
|
||||
sub = await Subject(name="Bar", category=cat).save()
|
||||
mixin_columns = ["created_date", "updated_date"]
|
||||
mixin2_columns = ["created_by", "updated_by"]
|
||||
assert all(field in Category.Meta.model_fields for field in mixin_columns)
|
||||
assert all(
|
||||
field in Category.ormar_config.model_fields for field in mixin_columns
|
||||
)
|
||||
assert cat.created_date is not None
|
||||
assert cat.updated_date is not None
|
||||
assert all(field in Subject.Meta.model_fields for field in mixin_columns)
|
||||
assert all(
|
||||
field in Subject.ormar_config.model_fields for field in mixin_columns
|
||||
)
|
||||
assert sub.created_date is not None
|
||||
assert sub.updated_date is not None
|
||||
|
||||
assert all(field in Category.Meta.model_fields for field in mixin2_columns)
|
||||
assert all(
|
||||
field not in Subject.Meta.model_fields for field in mixin2_columns
|
||||
field in Category.ormar_config.model_fields for field in mixin2_columns
|
||||
)
|
||||
assert all(
|
||||
field not in Subject.ormar_config.model_fields
|
||||
for field in mixin2_columns
|
||||
)
|
||||
|
||||
inspector = sa.inspect(engine)
|
||||
inspector = sa.inspect(base_ormar_config.engine)
|
||||
assert "categories" in inspector.get_table_names()
|
||||
table_columns = [x.get("name") for x in inspector.get_columns("categories")]
|
||||
assert all(col in table_columns for col in mixin_columns + mixin2_columns)
|
||||
@ -147,6 +141,7 @@ async def test_fields_inherited_from_mixin():
|
||||
assert round_date_to_seconds(sub2.created_date) == round_date_to_seconds(
|
||||
sub.created_date
|
||||
)
|
||||
assert sub2.category is not None
|
||||
assert sub2.category.updated_date is not None
|
||||
assert round_date_to_seconds(
|
||||
sub2.category.created_date
|
||||
|
||||
@ -1,32 +1,26 @@
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
import sqlalchemy as sa
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
from pydantic import computed_field
|
||||
|
||||
metadata = sa.MetaData()
|
||||
database = databases.Database(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class BaseFoo(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ormar.property_field
|
||||
@computed_field
|
||||
def prefixed_name(self) -> str:
|
||||
return "prefix_" + self.name
|
||||
|
||||
|
||||
class Foo(BaseFoo):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
@ormar.property_field
|
||||
@computed_field
|
||||
def double_prefixed_name(self) -> str:
|
||||
return "prefix2_" + self.name
|
||||
|
||||
@ -34,30 +28,22 @@ class Foo(BaseFoo):
|
||||
|
||||
|
||||
class Bar(BaseFoo):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
@ormar.property_field
|
||||
@computed_field
|
||||
def prefixed_name(self) -> str:
|
||||
return "baz_" + self.name
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_property_fields_are_inherited():
|
||||
foo = Foo(name="foo")
|
||||
assert foo.prefixed_name == "prefix_foo"
|
||||
assert foo.dict() == {
|
||||
assert foo.model_dump() == {
|
||||
"name": "foo",
|
||||
"id": None,
|
||||
"double_prefixed_name": "prefix2_foo",
|
||||
@ -66,4 +52,4 @@ def test_property_fields_are_inherited():
|
||||
|
||||
bar = Bar(name="bar")
|
||||
assert bar.prefixed_name == "baz_bar"
|
||||
assert bar.dict() == {"name": "bar", "id": None, "prefixed_name": "baz_bar"}
|
||||
assert bar.model_dump() == {"name": "bar", "id": None, "prefixed_name": "baz_bar"}
|
||||
|
||||
@ -1,25 +1,17 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import pytest
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class BaseModel(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: uuid.UUID = ormar.UUID(
|
||||
primary_key=True, default=uuid.uuid4, uuid_format="string"
|
||||
@ -29,36 +21,29 @@ class BaseModel(ormar.Model):
|
||||
|
||||
|
||||
class Member(BaseModel):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "members"
|
||||
ormar_config = base_ormar_config.copy(tablename="members")
|
||||
|
||||
first_name: str = ormar.String(max_length=50)
|
||||
last_name: str = ormar.String(max_length=50)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_model_structure():
|
||||
assert "id" in BaseModel.__fields__
|
||||
assert "id" in BaseModel.Meta.model_fields
|
||||
assert BaseModel.Meta.model_fields["id"].has_default()
|
||||
assert BaseModel.__fields__["id"].default_factory is not None
|
||||
assert "id" in BaseModel.model_fields
|
||||
assert "id" in BaseModel.ormar_config.model_fields
|
||||
assert BaseModel.ormar_config.model_fields["id"].has_default()
|
||||
assert BaseModel.model_fields["id"].default_factory is not None
|
||||
|
||||
assert "id" in Member.__fields__
|
||||
assert "id" in Member.Meta.model_fields
|
||||
assert Member.Meta.model_fields["id"].has_default()
|
||||
assert Member.__fields__["id"].default_factory is not None
|
||||
assert "id" in Member.model_fields
|
||||
assert "id" in Member.ormar_config.model_fields
|
||||
assert Member.ormar_config.model_fields["id"].has_default()
|
||||
assert Member.model_fields["id"].default_factory is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fields_inherited_with_default():
|
||||
async with database:
|
||||
async with base_ormar_config.database:
|
||||
await Member(first_name="foo", last_name="bar").save()
|
||||
await Member.objects.create(first_name="foo", last_name="bar")
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
import datetime
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import pytest
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class TableBase(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
created_by: str = ormar.String(max_length=20, default="test")
|
||||
@ -27,8 +22,7 @@ class TableBase(ormar.Model):
|
||||
|
||||
|
||||
class NationBase(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
name: str = ormar.String(max_length=50)
|
||||
alpha2_code: str = ormar.String(max_length=2)
|
||||
@ -37,22 +31,15 @@ class NationBase(ormar.Model):
|
||||
|
||||
|
||||
class Nation(NationBase, TableBase):
|
||||
class Meta(ormar.ModelMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_is_not_abstract_by_default():
|
||||
async with database:
|
||||
async with base_ormar_config.database:
|
||||
sweden = await Nation(
|
||||
name="Sweden", alpha2_code="SE", region="Europe", subregion="Scandinavia"
|
||||
).save()
|
||||
|
||||
@ -1,29 +1,20 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class Library(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Package(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
library: Library = ormar.ForeignKey(Library, related_name="packages")
|
||||
@ -31,8 +22,7 @@ class Package(ormar.Model):
|
||||
|
||||
|
||||
class Ticket(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
number: int = ormar.Integer()
|
||||
@ -40,8 +30,7 @@ class Ticket(ormar.Model):
|
||||
|
||||
|
||||
class TicketPackage(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
status: str = ormar.String(max_length=100)
|
||||
@ -49,11 +38,16 @@ class TicketPackage(ormar.Model):
|
||||
package: Package = ormar.ForeignKey(Package, related_name="tickets")
|
||||
|
||||
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_have_proper_children():
|
||||
TicketPackageOut = TicketPackage.get_pydantic(exclude={"ticket"})
|
||||
assert "package" in TicketPackageOut.__fields__
|
||||
PydanticPackage = TicketPackageOut.__fields__["package"].type_
|
||||
assert "library" in PydanticPackage.__fields__
|
||||
assert "package" in TicketPackageOut.model_fields
|
||||
PydanticPackage = TicketPackageOut.__pydantic_core_schema__["schema"]["fields"][
|
||||
"package"
|
||||
]["schema"]["schema"]["schema"]["cls"]
|
||||
assert "library" in PydanticPackage.model_fields
|
||||
|
||||
|
||||
def test_casts_properly():
|
||||
@ -69,7 +63,7 @@ def test_casts_properly():
|
||||
}
|
||||
test_package = TicketPackage(**payload)
|
||||
TicketPackageOut = TicketPackage.get_pydantic(exclude={"ticket"})
|
||||
parsed = TicketPackageOut(**test_package.dict()).dict()
|
||||
parsed = TicketPackageOut(**test_package.model_dump()).model_dump()
|
||||
assert "ticket" not in parsed
|
||||
assert "package" in parsed
|
||||
assert "library" in parsed.get("package")
|
||||
|
||||
@ -1,23 +1,13 @@
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class NewTestModel(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
a: int = ormar.Integer(primary_key=True)
|
||||
b: str = ormar.String(max_length=1)
|
||||
@ -27,15 +17,9 @@ class NewTestModel(ormar.Model):
|
||||
f: str = ormar.String(max_length=1)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_model_field_order():
|
||||
TestCreate = NewTestModel.get_pydantic(exclude={"a"})
|
||||
assert list(TestCreate.__fields__.keys()) == ["b", "c", "d", "e", "f"]
|
||||
assert list(TestCreate.model_fields.keys()) == ["b", "c", "d", "e", "f"]
|
||||
|
||||
@ -1,28 +1,26 @@
|
||||
import enum
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from pydantic import ValidationError
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import pytest
|
||||
from pydantic import ValidationError, field_validator
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class BaseModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
str_field: str = ormar.String(min_length=5, max_length=10, nullable=False)
|
||||
|
||||
@field_validator("str_field")
|
||||
def validate_str_field(cls, v):
|
||||
if " " not in v:
|
||||
raise ValueError("must contain a space")
|
||||
return v
|
||||
|
||||
|
||||
class EnumExample(str, enum.Enum):
|
||||
@ -32,24 +30,17 @@ class EnumExample(str, enum.Enum):
|
||||
|
||||
|
||||
class ModelExample(BaseModel):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "examples"
|
||||
ormar_config = base_ormar_config.copy(tablename="examples")
|
||||
|
||||
str_field: str = ormar.String(min_length=5, max_length=10, nullable=False)
|
||||
enum_field: str = ormar.String(
|
||||
max_length=1, nullable=False, choices=list(EnumExample)
|
||||
)
|
||||
|
||||
@pydantic.validator("str_field")
|
||||
def validate_str_field(cls, v):
|
||||
if " " not in v:
|
||||
raise ValueError("must contain a space")
|
||||
return v
|
||||
enum_field: str = ormar.Enum(enum_class=EnumExample, nullable=False)
|
||||
|
||||
|
||||
ModelExampleCreate = ModelExample.get_pydantic(exclude={"id"})
|
||||
|
||||
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_ormar_validator():
|
||||
ModelExample(str_field="a aaaaaa", enum_field="A")
|
||||
with pytest.raises(ValidationError) as e:
|
||||
@ -57,7 +48,7 @@ def test_ormar_validator():
|
||||
assert "must contain a space" in str(e)
|
||||
with pytest.raises(ValidationError) as e:
|
||||
ModelExample(str_field="a aaaaaaa", enum_field="Z")
|
||||
assert "not in allowed choices" in str(e)
|
||||
assert "Input should be 'A', 'B' or 'C'" in str(e)
|
||||
|
||||
|
||||
def test_pydantic_validator():
|
||||
@ -67,4 +58,4 @@ def test_pydantic_validator():
|
||||
assert "must contain a space" in str(e)
|
||||
with pytest.raises(ValidationError) as e:
|
||||
ModelExampleCreate(str_field="a aaaaaaa", enum_field="Z")
|
||||
assert "not in allowed choices" in str(e)
|
||||
assert "Input should be 'A', 'B' or 'C'" in str(e)
|
||||
|
||||
@ -1,22 +1,13 @@
|
||||
import enum
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from pydantic import ValidationError
|
||||
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import pytest
|
||||
from pydantic import ValidationError, field_validator
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL)
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
base_ormar_config = create_config()
|
||||
|
||||
|
||||
class EnumExample(str, enum.Enum):
|
||||
@ -26,18 +17,13 @@ class EnumExample(str, enum.Enum):
|
||||
|
||||
|
||||
class ModelExample(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
tablename = "examples"
|
||||
ormar_config = base_ormar_config.copy(tablename="examples")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
str_field: str = ormar.String(min_length=5, max_length=10, nullable=False)
|
||||
enum_field: str = ormar.String(
|
||||
max_length=1, nullable=False, choices=list(EnumExample)
|
||||
)
|
||||
enum_field: str = ormar.Enum(nullable=False, enum_class=EnumExample)
|
||||
|
||||
@pydantic.validator("str_field")
|
||||
@field_validator("str_field")
|
||||
def validate_str_field(cls, v):
|
||||
if " " not in v:
|
||||
raise ValueError("must contain a space")
|
||||
@ -47,6 +33,9 @@ class ModelExample(ormar.Model):
|
||||
ModelExampleCreate = ModelExample.get_pydantic(exclude={"id"})
|
||||
|
||||
|
||||
create_test_database = init_tests(base_ormar_config)
|
||||
|
||||
|
||||
def test_ormar_validator():
|
||||
ModelExample(str_field="a aaaaaa", enum_field="A")
|
||||
with pytest.raises(ValidationError) as e:
|
||||
@ -54,7 +43,7 @@ def test_ormar_validator():
|
||||
assert "must contain a space" in str(e)
|
||||
with pytest.raises(ValidationError) as e:
|
||||
ModelExample(str_field="a aaaaaaa", enum_field="Z")
|
||||
assert "not in allowed choices" in str(e)
|
||||
assert "Input should be 'A', 'B' or 'C'" in str(e)
|
||||
|
||||
|
||||
def test_pydantic_validator():
|
||||
@ -64,4 +53,4 @@ def test_pydantic_validator():
|
||||
assert "must contain a space" in str(e)
|
||||
with pytest.raises(ValidationError) as e:
|
||||
ModelExampleCreate(str_field="a aaaaaaa", enum_field="Z")
|
||||
assert "not in allowed choices" in str(e)
|
||||
assert "Input should be 'A', 'B' or 'C'" in str(e)
|
||||
|
||||
Reference in New Issue
Block a user