split tests into packages
This commit is contained in:
0
tests/test_model_definition/__init__.py
Normal file
0
tests/test_model_definition/__init__.py
Normal file
0
tests/test_model_definition/pks_and_fks/__init__.py
Normal file
0
tests/test_model_definition/pks_and_fks/__init__.py
Normal file
@ -0,0 +1,47 @@
|
||||
import random
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
def key():
|
||||
return "".join(random.choice("abcdefgh123456") for _ in range(8))
|
||||
|
||||
|
||||
class Model(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "models"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: str = ormar.String(primary_key=True, default=key, max_length=8)
|
||||
name: str = ormar.String(max_length=32)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="function")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pk_1():
|
||||
async with database:
|
||||
model = await Model.objects.create(name="NAME")
|
||||
assert isinstance(model.id, str)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pk_2():
|
||||
async with database:
|
||||
model = await Model.objects.create(name="NAME")
|
||||
assert await Model.objects.all() == [model]
|
||||
@ -0,0 +1,81 @@
|
||||
from random import choice
|
||||
from string import ascii_uppercase
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
import ormar
|
||||
from ormar import Float, String
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
def get_id() -> str:
|
||||
return "".join(choice(ascii_uppercase) for _ in range(12))
|
||||
|
||||
|
||||
class MainMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class PositionOrm(ormar.Model):
|
||||
class Meta(MainMeta):
|
||||
pass
|
||||
|
||||
name: str = String(primary_key=True, max_length=50)
|
||||
x: float = Float()
|
||||
y: float = Float()
|
||||
degrees: float = Float()
|
||||
|
||||
|
||||
class PositionOrmDef(ormar.Model):
|
||||
class Meta(MainMeta):
|
||||
pass
|
||||
|
||||
name: str = String(primary_key=True, max_length=50, default=get_id)
|
||||
x: float = Float()
|
||||
y: float = Float()
|
||||
degrees: float = Float()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
async def cleanup():
|
||||
yield
|
||||
async with database:
|
||||
await PositionOrm.objects.delete(each=True)
|
||||
await PositionOrmDef.objects.delete(each=True)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_creating_a_position(cleanup):
|
||||
async with database:
|
||||
instance = PositionOrm(name="my_pos", x=1.0, y=2.0, degrees=3.0,)
|
||||
await instance.save()
|
||||
assert instance.saved
|
||||
assert instance.name == "my_pos"
|
||||
|
||||
instance2 = PositionOrmDef(x=1.0, y=2.0, degrees=3.0,)
|
||||
await instance2.save()
|
||||
assert instance2.saved
|
||||
assert instance2.name is not None
|
||||
assert len(instance2.name) == 12
|
||||
|
||||
instance3 = PositionOrmDef(x=1.0, y=2.0, degrees=3.0,)
|
||||
await instance3.save()
|
||||
assert instance3.saved
|
||||
assert instance3.name is not None
|
||||
assert len(instance3.name) == 12
|
||||
assert instance2.name != instance3.name
|
||||
63
tests/test_model_definition/pks_and_fks/test_uuid_fks.py
Normal file
63
tests/test_model_definition/pks_and_fks/test_uuid_fks.py
Normal file
@ -0,0 +1,63 @@
|
||||
import uuid
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
db = databases.Database(DATABASE_URL)
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "user"
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id: uuid.UUID = ormar.UUID(
|
||||
primary_key=True, default=uuid.uuid4, uuid_format="string"
|
||||
)
|
||||
username = ormar.String(index=True, unique=True, null=False, max_length=255)
|
||||
email = ormar.String(index=True, unique=True, nullable=False, max_length=255)
|
||||
hashed_password = ormar.String(null=False, max_length=255)
|
||||
is_active = ormar.Boolean(default=True, nullable=False)
|
||||
is_superuser = ormar.Boolean(default=False, nullable=False)
|
||||
|
||||
|
||||
class Token(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "token"
|
||||
metadata = metadata
|
||||
database = db
|
||||
|
||||
id = ormar.Integer(primary_key=True)
|
||||
text = ormar.String(max_length=4, unique=True)
|
||||
user = ormar.ForeignKey(User, related_name="tokens")
|
||||
created_at = ormar.DateTime(server_default=sqlalchemy.func.now())
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uuid_fk():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
user = await User.objects.create(
|
||||
username="User1",
|
||||
email="email@example.com",
|
||||
hashed_password="^$EDACVS(&A&Y@2131aa",
|
||||
is_active=True,
|
||||
is_superuser=False,
|
||||
)
|
||||
await Token.objects.create(text="AAAA", user=user)
|
||||
await Token.objects.order_by("-created_at").all()
|
||||
178
tests/test_model_definition/test_aliases.py
Normal file
178
tests/test_model_definition/test_aliases.py
Normal file
@ -0,0 +1,178 @@
|
||||
from typing import List, Optional
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Child(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "children"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(name="child_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
last_name: str = ormar.String(name="lname", max_length=100)
|
||||
born_year: int = ormar.Integer(name="year_born", nullable=True)
|
||||
|
||||
|
||||
class Artist(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "artists"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(name="artist_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
last_name: str = ormar.String(name="lname", max_length=100)
|
||||
born_year: int = ormar.Integer(name="year")
|
||||
children: Optional[List[Child]] = ormar.ManyToMany(Child)
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "music_albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(name="album_id", primary_key=True)
|
||||
name: str = ormar.String(name="album_name", max_length=100)
|
||||
artist: Optional[Artist] = ormar.ForeignKey(Artist, name="artist_id")
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
def test_table_structure():
|
||||
assert "album_id" in [x.name for x in Album.Meta.table.columns]
|
||||
assert "album_name" in [x.name for x in Album.Meta.table.columns]
|
||||
assert "fname" in [x.name for x in Artist.Meta.table.columns]
|
||||
assert "lname" in [x.name for x in Artist.Meta.table.columns]
|
||||
assert "year" in [x.name for x in Artist.Meta.table.columns]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_working_with_aliases():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
artist = await Artist.objects.create(
|
||||
first_name="Ted", last_name="Mosbey", born_year=1975
|
||||
)
|
||||
await Album.objects.create(name="Aunt Robin", artist=artist)
|
||||
|
||||
await artist.children.create(
|
||||
first_name="Son", last_name="1", born_year=1990
|
||||
)
|
||||
await artist.children.create(
|
||||
first_name="Son", last_name="2", born_year=1995
|
||||
)
|
||||
|
||||
await artist.children.create(
|
||||
first_name="Son", last_name="3", born_year=1998
|
||||
)
|
||||
|
||||
album = await Album.objects.select_related("artist").first()
|
||||
assert album.artist.last_name == "Mosbey"
|
||||
|
||||
assert album.artist.id is not None
|
||||
assert album.artist.first_name == "Ted"
|
||||
assert album.artist.born_year == 1975
|
||||
|
||||
assert album.name == "Aunt Robin"
|
||||
|
||||
artist = await Artist.objects.select_related("children").get()
|
||||
assert len(artist.children) == 3
|
||||
assert artist.children[0].first_name == "Son"
|
||||
assert artist.children[1].last_name == "2"
|
||||
assert artist.children[2].last_name == "3"
|
||||
|
||||
await artist.update(last_name="Bundy")
|
||||
await Artist.objects.filter(pk=artist.pk).update(born_year=1974)
|
||||
|
||||
artist = await Artist.objects.select_related("children").get()
|
||||
assert artist.last_name == "Bundy"
|
||||
assert artist.born_year == 1974
|
||||
|
||||
artist = (
|
||||
await Artist.objects.select_related("children")
|
||||
.fields(
|
||||
[
|
||||
"first_name",
|
||||
"last_name",
|
||||
"born_year",
|
||||
"children__first_name",
|
||||
"children__last_name",
|
||||
]
|
||||
)
|
||||
.get()
|
||||
)
|
||||
assert artist.children[0].born_year is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_operations_and_fields():
|
||||
async with database:
|
||||
d1 = Child(first_name="Daughter", last_name="1", born_year=1990)
|
||||
d2 = Child(first_name="Daughter", last_name="2", born_year=1991)
|
||||
await Child.objects.bulk_create([d1, d2])
|
||||
|
||||
children = await Child.objects.filter(first_name="Daughter").all()
|
||||
assert len(children) == 2
|
||||
assert children[0].last_name == "1"
|
||||
|
||||
for child in children:
|
||||
child.born_year = child.born_year - 100
|
||||
|
||||
await Child.objects.bulk_update(children)
|
||||
|
||||
children = await Child.objects.filter(first_name="Daughter").all()
|
||||
assert len(children) == 2
|
||||
assert children[0].born_year == 1890
|
||||
|
||||
children = await Child.objects.fields(["first_name", "last_name"]).all()
|
||||
assert len(children) == 2
|
||||
for child in children:
|
||||
assert child.born_year is None
|
||||
|
||||
await children[0].load()
|
||||
await children[0].delete()
|
||||
children = await Child.objects.all()
|
||||
assert len(children) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_working_with_aliases_get_or_create():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
artist = await Artist.objects.get_or_create(
|
||||
first_name="Teddy", last_name="Bear", born_year=2020
|
||||
)
|
||||
assert artist.pk is not None
|
||||
|
||||
artist2 = await Artist.objects.get_or_create(
|
||||
first_name="Teddy", last_name="Bear", born_year=2020
|
||||
)
|
||||
assert artist == artist2
|
||||
|
||||
art3 = artist2.dict()
|
||||
art3["born_year"] = 2019
|
||||
await Artist.objects.update_or_create(**art3)
|
||||
|
||||
artist3 = await Artist.objects.get(last_name="Bear")
|
||||
assert artist3.born_year == 2019
|
||||
|
||||
artists = await Artist.objects.all()
|
||||
assert len(artists) == 1
|
||||
61
tests/test_model_definition/test_columns.py
Normal file
61
tests/test_model_definition/test_columns.py
Normal file
@ -0,0 +1,61 @@
|
||||
import datetime
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
def time():
|
||||
return datetime.datetime.now().time()
|
||||
|
||||
|
||||
class Example(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "example"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=200, default="aaa")
|
||||
created: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
created_day: datetime.date = ormar.Date(default=datetime.date.today)
|
||||
created_time: datetime.time = ormar.Time(default=time)
|
||||
description: str = ormar.Text(nullable=True)
|
||||
value: float = ormar.Float(nullable=True)
|
||||
data: pydantic.Json = ormar.JSON(default={})
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_crud():
|
||||
async with database:
|
||||
example = Example()
|
||||
await example.save()
|
||||
|
||||
await example.load()
|
||||
assert example.created.year == datetime.datetime.now().year
|
||||
assert example.created_day == datetime.date.today()
|
||||
assert example.description is None
|
||||
assert example.value is None
|
||||
assert example.data == {}
|
||||
|
||||
await example.update(data={"foo": 123}, value=123.456)
|
||||
await example.load()
|
||||
assert example.value == 123.456
|
||||
assert example.data == {"foo": 123}
|
||||
|
||||
await example.delete()
|
||||
242
tests/test_model_definition/test_model_definition.py
Normal file
242
tests/test_model_definition/test_model_definition.py
Normal file
@ -0,0 +1,242 @@
|
||||
# type: ignore
|
||||
import asyncio
|
||||
import datetime
|
||||
import decimal
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
import typing
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import ModelDefinitionError
|
||||
from ormar.models import Model
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
|
||||
|
||||
class ExampleModel(Model):
|
||||
class Meta:
|
||||
tablename = "example"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
test: int = ormar.Integer(primary_key=True)
|
||||
test_string: str = ormar.String(max_length=250)
|
||||
test_text: str = ormar.Text(default="")
|
||||
test_bool: bool = ormar.Boolean(nullable=False)
|
||||
test_float = ormar.Float(nullable=True)
|
||||
test_datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
test_date = ormar.Date(default=datetime.date.today)
|
||||
test_time = ormar.Time(default=datetime.time)
|
||||
test_json = ormar.JSON(default={})
|
||||
test_bigint: int = ormar.BigInteger(default=0)
|
||||
test_decimal = ormar.Decimal(scale=2, precision=10)
|
||||
test_decimal2 = ormar.Decimal(max_digits=10, decimal_places=2)
|
||||
|
||||
|
||||
fields_to_check = [
|
||||
"test",
|
||||
"test_text",
|
||||
"test_string",
|
||||
"test_datetime",
|
||||
"test_date",
|
||||
"test_text",
|
||||
"test_float",
|
||||
"test_bigint",
|
||||
"test_json",
|
||||
]
|
||||
|
||||
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "examples"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
test: int = ormar.Integer(primary_key=True)
|
||||
test_string: str = ormar.String(max_length=250)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def event_loop():
|
||||
loop = asyncio.get_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
async def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def example():
|
||||
return ExampleModel(
|
||||
pk=1,
|
||||
test_string="test",
|
||||
test_bool=True,
|
||||
test_decimal=decimal.Decimal(3.5),
|
||||
test_decimal2=decimal.Decimal(5.5),
|
||||
)
|
||||
|
||||
|
||||
def test_not_nullable_field_is_required():
|
||||
with pytest.raises(pydantic.error_wrappers.ValidationError):
|
||||
ExampleModel(test=1, test_string="test")
|
||||
|
||||
|
||||
def test_model_attribute_access(example):
|
||||
assert example.test == 1
|
||||
assert example.test_string == "test"
|
||||
assert example.test_datetime.year == datetime.datetime.now().year
|
||||
assert example.test_date == datetime.date.today()
|
||||
assert example.test_text == ""
|
||||
assert example.test_float is None
|
||||
assert example.test_bigint == 0
|
||||
assert example.test_json == {}
|
||||
assert example.test_decimal == 3.5
|
||||
assert example.test_decimal2 == 5.5
|
||||
|
||||
example.test = 12
|
||||
assert example.test == 12
|
||||
|
||||
example._orm_saved = True
|
||||
assert example._orm_saved
|
||||
|
||||
|
||||
def test_model_attribute_json_access(example):
|
||||
example.test_json = dict(aa=12)
|
||||
assert example.test_json == dict(aa=12)
|
||||
|
||||
|
||||
def test_missing_metadata():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class JsonSample2(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "jsons2"
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
test_json = ormar.JSON(nullable=True)
|
||||
|
||||
|
||||
def test_missing_database():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class JsonSample3(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "jsons3"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
test_json = ormar.JSON(nullable=True)
|
||||
|
||||
|
||||
def test_non_existing_attr(example):
|
||||
with pytest.raises(ValueError):
|
||||
example.new_attr = 12
|
||||
|
||||
|
||||
def test_primary_key_access_and_setting(example):
|
||||
assert example.pk == 1
|
||||
example.pk = 2
|
||||
|
||||
assert example.pk == 2
|
||||
assert example.test == 2
|
||||
|
||||
|
||||
def test_pydantic_model_is_created(example):
|
||||
assert issubclass(example.__class__, pydantic.BaseModel)
|
||||
assert all([field in example.__fields__ for field in fields_to_check])
|
||||
assert example.test == 1
|
||||
|
||||
|
||||
def test_sqlalchemy_table_is_created(example):
|
||||
assert issubclass(example.Meta.table.__class__, sqlalchemy.Table)
|
||||
assert all([field in example.Meta.table.columns for field in fields_to_check])
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def test_no_pk_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError): # type: ignore
|
||||
|
||||
class ExampleModel2(Model): # type: ignore
|
||||
class Meta:
|
||||
tablename = "example2"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
test_string: str = ormar.String(max_length=250) # type: ignore
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def test_two_pks_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
@typing.no_type_check
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example3"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
test_string: str = ormar.String(max_length=250, primary_key=True)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def test_setting_pk_column_as_pydantic_only_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example4"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
test: int = ormar.Integer(primary_key=True, pydantic_only=True)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def test_decimal_error_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example5"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
test: decimal.Decimal = ormar.Decimal(primary_key=True)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def test_string_error_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example6"
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
test: str = ormar.String(primary_key=True)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def test_json_conversion_in_model():
|
||||
with pytest.raises(pydantic.ValidationError):
|
||||
ExampleModel(
|
||||
test_json=datetime.datetime.now(),
|
||||
test=1,
|
||||
test_string="test",
|
||||
test_bool=True,
|
||||
)
|
||||
537
tests/test_model_definition/test_models.py
Normal file
537
tests/test_model_definition/test_models.py
Normal file
@ -0,0 +1,537 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
import datetime
|
||||
from typing import List
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import QueryDefinitionError, NoMatch, ModelError
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class JsonSample(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "jsons"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
test_json = ormar.JSON(nullable=True)
|
||||
|
||||
|
||||
class UUIDSample(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "uuids"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4)
|
||||
test_text: str = ormar.Text()
|
||||
|
||||
|
||||
class UUIDSample2(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "uuids2"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: uuid.UUID = ormar.UUID(
|
||||
primary_key=True, default=uuid.uuid4, uuid_format="string"
|
||||
)
|
||||
test_text: str = ormar.Text()
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "users"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="")
|
||||
|
||||
|
||||
class User2(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "users2"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: str = ormar.String(primary_key=True, max_length=100)
|
||||
name: str = ormar.String(max_length=100, default="")
|
||||
|
||||
|
||||
class Product(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "product"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
rating: int = ormar.Integer(minimum=1, maximum=5)
|
||||
in_stock: bool = ormar.Boolean(default=False)
|
||||
last_delivery: datetime.date = ormar.Date(default=datetime.datetime.now)
|
||||
|
||||
|
||||
country_name_choices = ("Canada", "Algeria", "United States", "Belize")
|
||||
country_taxed_choices = (True,)
|
||||
country_country_code_choices = (-10, 1, 213, 1200)
|
||||
|
||||
|
||||
class Country(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "country"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(
|
||||
max_length=9, choices=country_name_choices, default="Canada",
|
||||
)
|
||||
taxed: bool = ormar.Boolean(choices=country_taxed_choices, default=True)
|
||||
country_code: int = ormar.Integer(
|
||||
minimum=0, maximum=1000, choices=country_country_code_choices, default=1
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def event_loop():
|
||||
loop = asyncio.get_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
async def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
def test_model_class():
|
||||
assert list(User.Meta.model_fields.keys()) == ["id", "name"]
|
||||
assert issubclass(User.Meta.model_fields["id"].__class__, pydantic.fields.FieldInfo)
|
||||
assert User.Meta.model_fields["id"].primary_key is True
|
||||
assert isinstance(User.Meta.model_fields["name"], pydantic.fields.FieldInfo)
|
||||
assert User.Meta.model_fields["name"].max_length == 100
|
||||
assert isinstance(User.Meta.table, sqlalchemy.Table)
|
||||
|
||||
|
||||
def test_wrong_field_name():
|
||||
with pytest.raises(ModelError):
|
||||
User(non_existing_pk=1)
|
||||
|
||||
|
||||
def test_model_pk():
|
||||
user = User(pk=1)
|
||||
assert user.pk == 1
|
||||
assert user.id == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_column():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await JsonSample.objects.create(test_json=dict(aa=12))
|
||||
await JsonSample.objects.create(test_json='{"aa": 12}')
|
||||
|
||||
items = await JsonSample.objects.all()
|
||||
assert len(items) == 2
|
||||
assert items[0].test_json == dict(aa=12)
|
||||
assert items[1].test_json == dict(aa=12)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uuid_column():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
u1 = await UUIDSample.objects.create(test_text="aa")
|
||||
u2 = await UUIDSample.objects.create(test_text="bb")
|
||||
|
||||
items = await UUIDSample.objects.all()
|
||||
assert len(items) == 2
|
||||
|
||||
assert isinstance(items[0].id, uuid.UUID)
|
||||
assert isinstance(items[1].id, uuid.UUID)
|
||||
|
||||
assert items[0].id in (u1.id, u2.id)
|
||||
assert items[1].id in (u1.id, u2.id)
|
||||
|
||||
assert items[0].id != items[1].id
|
||||
|
||||
item = await UUIDSample.objects.filter(id=u1.id).get()
|
||||
assert item.id == u1.id
|
||||
|
||||
item2 = await UUIDSample.objects.first()
|
||||
item3 = await UUIDSample.objects.get(pk=item2.id)
|
||||
assert item2.id == item3.id
|
||||
assert isinstance(item3.id, uuid.UUID)
|
||||
|
||||
u3 = await UUIDSample2(**u1.dict()).save()
|
||||
|
||||
u1_2 = await UUIDSample.objects.get(pk=u3.id)
|
||||
assert u1_2 == u1
|
||||
|
||||
u4 = await UUIDSample2.objects.get(pk=u3.id)
|
||||
assert u3 == u4
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_crud():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
users = await User.objects.all()
|
||||
assert users == []
|
||||
|
||||
user = await User.objects.create(name="Tom")
|
||||
users = await User.objects.all()
|
||||
assert user.name == "Tom"
|
||||
assert user.pk is not None
|
||||
assert users == [user]
|
||||
|
||||
lookup = await User.objects.get()
|
||||
assert lookup == user
|
||||
|
||||
await user.update(name="Jane")
|
||||
users = await User.objects.all()
|
||||
assert user.name == "Jane"
|
||||
assert user.pk is not None
|
||||
assert users == [user]
|
||||
|
||||
await user.delete()
|
||||
users = await User.objects.all()
|
||||
assert users == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_get():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
with pytest.raises(ormar.NoMatch):
|
||||
await User.objects.get()
|
||||
|
||||
assert await User.objects.get_or_none() is None
|
||||
|
||||
user = await User.objects.create(name="Tom")
|
||||
lookup = await User.objects.get()
|
||||
assert lookup == user
|
||||
|
||||
user = await User.objects.create(name="Jane")
|
||||
await User.objects.create(name="Jane")
|
||||
with pytest.raises(ormar.MultipleMatches):
|
||||
await User.objects.get(name="Jane")
|
||||
|
||||
same_user = await User.objects.get(pk=user.id)
|
||||
assert same_user.id == user.id
|
||||
assert same_user.pk == user.pk
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_filter():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Jane")
|
||||
await User.objects.create(name="Lucy")
|
||||
|
||||
user = await User.objects.get(name="Lucy")
|
||||
assert user.name == "Lucy"
|
||||
|
||||
with pytest.raises(ormar.NoMatch):
|
||||
await User.objects.get(name="Jim")
|
||||
|
||||
await Product.objects.create(name="T-Shirt", rating=5, in_stock=True)
|
||||
await Product.objects.create(name="Dress", rating=4)
|
||||
await Product.objects.create(name="Coat", rating=3, in_stock=True)
|
||||
|
||||
product = await Product.objects.get(name__iexact="t-shirt", rating=5)
|
||||
assert product.pk is not None
|
||||
assert product.name == "T-Shirt"
|
||||
assert product.rating == 5
|
||||
assert product.last_delivery == datetime.datetime.now().date()
|
||||
|
||||
products = await Product.objects.all(rating__gte=2, in_stock=True)
|
||||
assert len(products) == 2
|
||||
|
||||
products = await Product.objects.all(name__icontains="T")
|
||||
assert len(products) == 2
|
||||
|
||||
products = await Product.objects.exclude(rating__gte=4).all()
|
||||
assert len(products) == 1
|
||||
|
||||
products = await Product.objects.exclude(rating__gte=4, in_stock=True).all()
|
||||
assert len(products) == 2
|
||||
|
||||
products = await Product.objects.exclude(in_stock=True).all()
|
||||
assert len(products) == 1
|
||||
|
||||
products = await Product.objects.exclude(name__icontains="T").all()
|
||||
assert len(products) == 1
|
||||
|
||||
# Test escaping % character from icontains, contains, and iexact
|
||||
await Product.objects.create(name="100%-Cotton", rating=3)
|
||||
await Product.objects.create(name="Cotton-100%-Egyptian", rating=3)
|
||||
await Product.objects.create(name="Cotton-100%", rating=3)
|
||||
products = Product.objects.filter(name__iexact="100%-cotton")
|
||||
assert await products.count() == 1
|
||||
|
||||
products = Product.objects.filter(name__contains="%")
|
||||
assert await products.count() == 3
|
||||
|
||||
products = Product.objects.filter(name__icontains="%")
|
||||
assert await products.count() == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrong_query_contains_model():
|
||||
async with database:
|
||||
with pytest.raises(QueryDefinitionError):
|
||||
product = Product(name="90%-Cotton", rating=2)
|
||||
await Product.objects.filter(name__contains=product).count()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_exists():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
assert await User.objects.filter(name="Tom").exists() is True
|
||||
assert await User.objects.filter(name="Jane").exists() is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_count():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Jane")
|
||||
await User.objects.create(name="Lucy")
|
||||
|
||||
assert await User.objects.count() == 3
|
||||
assert await User.objects.filter(name__icontains="T").count() == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_limit():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Jane")
|
||||
await User.objects.create(name="Lucy")
|
||||
|
||||
assert len(await User.objects.limit(2).all()) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_limit_with_filter():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Tom")
|
||||
|
||||
assert (
|
||||
len(await User.objects.limit(2).filter(name__iexact="Tom").all()) == 2
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_offset():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Jane")
|
||||
|
||||
users = await User.objects.offset(1).limit(1).all()
|
||||
assert users[0].name == "Jane"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_first():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
tom = await User.objects.create(name="Tom")
|
||||
jane = await User.objects.create(name="Jane")
|
||||
|
||||
assert await User.objects.first() == tom
|
||||
assert await User.objects.first(name="Jane") == jane
|
||||
assert await User.objects.filter(name="Jane").first() == jane
|
||||
with pytest.raises(NoMatch):
|
||||
await User.objects.filter(name="Lucy").first()
|
||||
|
||||
|
||||
def not_contains(a, b):
|
||||
return a not in b
|
||||
|
||||
|
||||
def contains(a, b):
|
||||
return a in b
|
||||
|
||||
|
||||
def check_choices(values: tuple, ops: List):
|
||||
ops_dict = {"in": contains, "out": not_contains}
|
||||
checks = (country_name_choices, country_taxed_choices, country_country_code_choices)
|
||||
assert all(
|
||||
[ops_dict[op](value, check) for value, op, check in zip(values, ops, checks)]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_choices():
|
||||
"""Test that choices work properly for various types of fields."""
|
||||
async with database:
|
||||
# Test valid choices.
|
||||
await asyncio.gather(
|
||||
Country.objects.create(name="Canada", taxed=True, country_code=1),
|
||||
Country.objects.create(name="Algeria", taxed=True, country_code=213),
|
||||
Country.objects.create(name="Algeria"),
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Saudi Arabia", True, 1
|
||||
check_choices((name, taxed, country_code), ["out", "in", "in"])
|
||||
await Country.objects.create(
|
||||
name=name, taxed=taxed, country_code=country_code
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Algeria", False, 1
|
||||
check_choices((name, taxed, country_code), ["in", "out", "in"])
|
||||
await Country.objects.create(
|
||||
name=name, taxed=taxed, country_code=country_code
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Algeria", True, 967
|
||||
check_choices((name, taxed, country_code), ["in", "in", "out"])
|
||||
await Country.objects.create(
|
||||
name=name, taxed=taxed, country_code=country_code
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = (
|
||||
"United States",
|
||||
True,
|
||||
1,
|
||||
) # name is too long but is a valid choice
|
||||
check_choices((name, taxed, country_code), ["in", "in", "in"])
|
||||
await Country.objects.create(
|
||||
name=name, taxed=taxed, country_code=country_code
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = (
|
||||
"Algeria",
|
||||
True,
|
||||
-10,
|
||||
) # country code is too small but is a valid choice
|
||||
check_choices((name, taxed, country_code), ["in", "in", "in"])
|
||||
await Country.objects.create(
|
||||
name=name, taxed=taxed, country_code=country_code
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = (
|
||||
"Algeria",
|
||||
True,
|
||||
1200,
|
||||
) # country code is too large but is a valid choice
|
||||
check_choices((name, taxed, country_code), ["in", "in", "in"])
|
||||
await Country.objects.create(
|
||||
name=name, taxed=taxed, country_code=country_code
|
||||
)
|
||||
|
||||
# test setting after init also triggers validation
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Algeria", True, 967
|
||||
check_choices((name, taxed, country_code), ["in", "in", "out"])
|
||||
country = Country()
|
||||
country.country_code = country_code
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Saudi Arabia", True, 1
|
||||
check_choices((name, taxed, country_code), ["out", "in", "in"])
|
||||
country = Country()
|
||||
country.name = name
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Algeria", False, 1
|
||||
check_choices((name, taxed, country_code), ["in", "out", "in"])
|
||||
country = Country()
|
||||
country.taxed = taxed
|
||||
|
||||
# check also update from queryset
|
||||
with pytest.raises(ValueError):
|
||||
name, taxed, country_code = "Algeria", False, 1
|
||||
check_choices((name, taxed, country_code), ["in", "out", "in"])
|
||||
await Country(name="Belize").save()
|
||||
await Country.objects.filter(name="Belize").update(name="Vietnam")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_and_end_filters():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Markos Uj")
|
||||
await User.objects.create(name="Maqua Bigo")
|
||||
await User.objects.create(name="maqo quidid")
|
||||
await User.objects.create(name="Louis Figo")
|
||||
await User.objects.create(name="Loordi Kami")
|
||||
await User.objects.create(name="Yuuki Sami")
|
||||
|
||||
users = await User.objects.filter(name__startswith="Mar").all()
|
||||
assert len(users) == 1
|
||||
|
||||
users = await User.objects.filter(name__istartswith="ma").all()
|
||||
assert len(users) == 3
|
||||
|
||||
users = await User.objects.filter(name__istartswith="Maq").all()
|
||||
assert len(users) == 2
|
||||
|
||||
users = await User.objects.filter(name__iendswith="AMI").all()
|
||||
assert len(users) == 2
|
||||
|
||||
users = await User.objects.filter(name__endswith="Uj").all()
|
||||
assert len(users) == 1
|
||||
|
||||
users = await User.objects.filter(name__endswith="igo").all()
|
||||
assert len(users) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_and_first():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await User.objects.create(name="Tom")
|
||||
await User.objects.create(name="Jane")
|
||||
await User.objects.create(name="Lucy")
|
||||
await User.objects.create(name="Zack")
|
||||
await User.objects.create(name="Ula")
|
||||
|
||||
user = await User.objects.get()
|
||||
assert user.name == "Ula"
|
||||
|
||||
user = await User.objects.first()
|
||||
assert user.name == "Tom"
|
||||
|
||||
await User2.objects.create(id="Tom", name="Tom")
|
||||
await User2.objects.create(id="Jane", name="Jane")
|
||||
await User2.objects.create(id="Lucy", name="Lucy")
|
||||
await User2.objects.create(id="Zack", name="Zack")
|
||||
await User2.objects.create(id="Ula", name="Ula")
|
||||
|
||||
user = await User2.objects.get()
|
||||
assert user.name == "Zack"
|
||||
|
||||
user = await User2.objects.first()
|
||||
assert user.name == "Jane"
|
||||
79
tests/test_model_definition/test_properties.py
Normal file
79
tests/test_model_definition/test_properties.py
Normal file
@ -0,0 +1,79 @@
|
||||
# type: ignore
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar import ModelDefinitionError, property_field
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Song(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "songs"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
sort_order: int = ormar.Integer()
|
||||
|
||||
@property_field
|
||||
def sorted_name(self):
|
||||
return f"{self.sort_order}: {self.name}"
|
||||
|
||||
@property_field
|
||||
def sample(self):
|
||||
return "sample"
|
||||
|
||||
@property_field
|
||||
def sample2(self):
|
||||
return "sample2"
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sort_order_on_main_model():
|
||||
async with database:
|
||||
await Song.objects.create(name="Song 3", sort_order=3)
|
||||
await Song.objects.create(name="Song 1", sort_order=1)
|
||||
await Song.objects.create(name="Song 2", sort_order=2)
|
||||
|
||||
songs = await Song.objects.all()
|
||||
song_dict = [song.dict() for song in songs]
|
||||
assert all("sorted_name" in x for x in song_dict)
|
||||
assert all(
|
||||
x["sorted_name"] == f"{x['sort_order']}: {x['name']}" for x in song_dict
|
||||
)
|
||||
song_json = [song.json() for song in songs]
|
||||
assert all("sorted_name" in x for x in song_json)
|
||||
|
||||
check_include = songs[0].dict(include={"sample"})
|
||||
assert "sample" in check_include
|
||||
assert "sample2" not in check_include
|
||||
assert "sorted_name" not in check_include
|
||||
|
||||
check_include = songs[0].dict(exclude={"sample"})
|
||||
assert "sample" not in check_include
|
||||
assert "sample2" in check_include
|
||||
assert "sorted_name" in check_include
|
||||
|
||||
|
||||
def test_wrong_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
|
||||
class WrongModel(ormar.Model): # pragma: no cover
|
||||
@property_field
|
||||
def test(self, aa=10, bb=30):
|
||||
pass
|
||||
79
tests/test_model_definition/test_pydantic_only_fields.py
Normal file
79
tests/test_model_definition/test_pydantic_only_fields.py
Normal file
@ -0,0 +1,79 @@
|
||||
import datetime
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar import property_field
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
timestamp: datetime.datetime = ormar.DateTime(pydantic_only=True)
|
||||
|
||||
@property_field
|
||||
def name10(self) -> str:
|
||||
return self.name + "_10"
|
||||
|
||||
@property_field
|
||||
def name20(self) -> str:
|
||||
return self.name + "_20"
|
||||
|
||||
@property
|
||||
def name30(self) -> str:
|
||||
return self.name + "_30"
|
||||
|
||||
@property_field
|
||||
def name40(self) -> str:
|
||||
return self.name + "_40"
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pydantic_only_fields():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
album = await Album.objects.create(name="Hitchcock")
|
||||
assert album.pk is not None
|
||||
assert album.saved
|
||||
assert album.timestamp is None
|
||||
|
||||
album = await Album.objects.exclude_fields("timestamp").get()
|
||||
assert album.timestamp is None
|
||||
|
||||
album = await Album.objects.fields({"name", "timestamp"}).get()
|
||||
assert album.timestamp is None
|
||||
|
||||
test_dict = album.dict()
|
||||
assert "timestamp" in test_dict
|
||||
assert test_dict["timestamp"] is None
|
||||
|
||||
assert album.name30 == "Hitchcock_30"
|
||||
|
||||
album.timestamp = datetime.datetime.now()
|
||||
test_dict = album.dict()
|
||||
assert "timestamp" in test_dict
|
||||
assert test_dict["timestamp"] is not None
|
||||
assert test_dict.get("name10") == "Hitchcock_10"
|
||||
assert test_dict.get("name20") == "Hitchcock_20"
|
||||
assert test_dict.get("name40") == "Hitchcock_40"
|
||||
assert "name30" not in test_dict
|
||||
257
tests/test_model_definition/test_save_status.py
Normal file
257
tests/test_model_definition/test_save_status.py
Normal file
@ -0,0 +1,257 @@
|
||||
from typing import List
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import ModelPersistenceError
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class NickNames(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "nicks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="hq_name")
|
||||
is_lame: bool = ormar.Boolean(nullable=True)
|
||||
|
||||
|
||||
class NicksHq(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "nicks_x_hq"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class HQ(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "hqs"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="hq_name")
|
||||
nicks: List[NickNames] = ormar.ManyToMany(NickNames, through=NicksHq)
|
||||
|
||||
|
||||
class Company(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "companies"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="company_name")
|
||||
founded: int = ormar.Integer(nullable=True)
|
||||
hq: HQ = ormar.ForeignKey(HQ)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_instantation_false_save_true():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
comp = Company(name="Banzai", founded=1988)
|
||||
assert not comp.saved
|
||||
await comp.save()
|
||||
assert comp.saved
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_saved_edited_not_saved():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
comp = await Company.objects.create(name="Banzai", founded=1988)
|
||||
assert comp.saved
|
||||
comp.name = "Banzai2"
|
||||
assert not comp.saved
|
||||
|
||||
await comp.update()
|
||||
assert comp.saved
|
||||
|
||||
await comp.update(name="Banzai3")
|
||||
assert comp.saved
|
||||
|
||||
comp.pk = 999
|
||||
assert not comp.saved
|
||||
|
||||
await comp.update()
|
||||
assert comp.saved
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adding_related_gets_dirty():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
hq = await HQ.objects.create(name="Main")
|
||||
comp = await Company.objects.create(name="Banzai", founded=1988)
|
||||
assert comp.saved
|
||||
|
||||
comp.hq = hq
|
||||
assert not comp.saved
|
||||
await comp.update()
|
||||
assert comp.saved
|
||||
|
||||
comp = await Company.objects.select_related("hq").get(name="Banzai")
|
||||
assert comp.saved
|
||||
|
||||
assert comp.hq.pk == hq.pk
|
||||
assert comp.hq.saved
|
||||
|
||||
comp.hq.name = "Suburbs"
|
||||
assert not comp.hq.saved
|
||||
assert comp.saved
|
||||
|
||||
await comp.hq.update()
|
||||
assert comp.hq.saved
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adding_many_to_many_does_not_gets_dirty():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
nick1 = await NickNames.objects.create(name="Bazinga", is_lame=False)
|
||||
nick2 = await NickNames.objects.create(name="Bazinga2", is_lame=True)
|
||||
|
||||
hq = await HQ.objects.create(name="Main")
|
||||
assert hq.saved
|
||||
|
||||
await hq.nicks.add(nick1)
|
||||
assert hq.saved
|
||||
await hq.nicks.add(nick2)
|
||||
assert hq.saved
|
||||
|
||||
hq = await HQ.objects.select_related("nicks").get(name="Main")
|
||||
assert hq.saved
|
||||
assert hq.nicks[0].saved
|
||||
|
||||
await hq.nicks.remove(nick1)
|
||||
assert hq.saved
|
||||
|
||||
hq.nicks[0].name = "Kabucha"
|
||||
assert not hq.nicks[0].saved
|
||||
|
||||
await hq.nicks[0].update()
|
||||
assert hq.nicks[0].saved
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
comp = await Company.objects.create(name="Banzai", founded=1988)
|
||||
assert comp.saved
|
||||
await comp.delete()
|
||||
assert not comp.saved
|
||||
|
||||
await comp.update()
|
||||
assert comp.saved
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
comp = await Company.objects.create(name="Banzai", founded=1988)
|
||||
assert comp.saved
|
||||
comp.name = "AA"
|
||||
assert not comp.saved
|
||||
|
||||
await comp.load()
|
||||
assert comp.saved
|
||||
assert comp.name == "Banzai"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_queryset_methods():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
await Company.objects.create(name="Banzai", founded=1988)
|
||||
await Company.objects.create(name="Yuhu", founded=1989)
|
||||
await Company.objects.create(name="Konono", founded=1990)
|
||||
await Company.objects.create(name="Sumaaa", founded=1991)
|
||||
|
||||
comp = await Company.objects.get(name="Banzai")
|
||||
assert comp.saved
|
||||
|
||||
comp = await Company.objects.first()
|
||||
assert comp.saved
|
||||
|
||||
comps = await Company.objects.all()
|
||||
assert [comp.saved for comp in comps]
|
||||
|
||||
comp2 = await Company.objects.get_or_create(name="Banzai_new", founded=2001)
|
||||
assert comp2.saved
|
||||
|
||||
comp3 = await Company.objects.get_or_create(name="Banzai", founded=1988)
|
||||
assert comp3.saved
|
||||
assert comp3.pk == comp.pk
|
||||
|
||||
update_dict = comp.dict()
|
||||
update_dict["founded"] = 2010
|
||||
comp = await Company.objects.update_or_create(**update_dict)
|
||||
assert comp.saved
|
||||
assert comp.founded == 2010
|
||||
|
||||
create_dict = {"name": "Yoko", "founded": 2005}
|
||||
comp = await Company.objects.update_or_create(**create_dict)
|
||||
assert comp.saved
|
||||
assert comp.founded == 2005
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_methods():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
c1 = Company(name="Banzai", founded=1988)
|
||||
c2 = Company(name="Yuhu", founded=1989)
|
||||
|
||||
await Company.objects.bulk_create([c1, c2])
|
||||
assert c1.saved
|
||||
assert c2.saved
|
||||
|
||||
c1, c2 = await Company.objects.all()
|
||||
c1.name = "Banzai2"
|
||||
c2.name = "Yuhu2"
|
||||
|
||||
assert not c1.saved
|
||||
assert not c2.saved
|
||||
|
||||
await Company.objects.bulk_update([c1, c2])
|
||||
assert c1.saved
|
||||
assert c2.saved
|
||||
|
||||
c3 = Company(name="Cobra", founded=2088)
|
||||
assert not c3.saved
|
||||
|
||||
with pytest.raises(ModelPersistenceError):
|
||||
await c3.update()
|
||||
|
||||
await c3.upsert()
|
||||
assert c3.saved
|
||||
|
||||
c3.name = "Python"
|
||||
assert not c3.saved
|
||||
|
||||
await c3.upsert()
|
||||
assert c3.saved
|
||||
assert c3.name == "Python"
|
||||
|
||||
await c3.upsert(founded=2077)
|
||||
assert c3.saved
|
||||
assert c3.founded == 2077
|
||||
66
tests/test_model_definition/test_saving_nullable_fields.py
Normal file
66
tests/test_model_definition/test_saving_nullable_fields.py
Normal file
@ -0,0 +1,66 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
import ormar
|
||||
import pytest
|
||||
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
db = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class PrimaryModel(ormar.Model):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = db
|
||||
tablename = "primary_models"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=255, index=True)
|
||||
some_text: str = ormar.Text()
|
||||
# NOTE: Removing nullable=True makes the test pass.
|
||||
some_other_text: Optional[str] = ormar.Text(nullable=True)
|
||||
|
||||
|
||||
class SecondaryModel(ormar.Model):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = db
|
||||
tablename = "secondary_models"
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
primary_model: PrimaryModel = ormar.ForeignKey(
|
||||
PrimaryModel, related_name="secondary_models",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
def create_test_database():
|
||||
engine = create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_models():
|
||||
async with db:
|
||||
async with db.transaction(force_rollback=True):
|
||||
primary = await PrimaryModel(
|
||||
name="Foo", some_text="Bar", some_other_text="Baz"
|
||||
).save()
|
||||
assert primary.id == 1
|
||||
|
||||
secondary = await SecondaryModel(name="Foo", primary_model=primary).save()
|
||||
assert secondary.id == 1
|
||||
assert secondary.primary_model.id == 1
|
||||
|
||||
secondary = await SecondaryModel.objects.get()
|
||||
assert secondary.name == "Foo"
|
||||
await secondary.update(name="Updated")
|
||||
assert secondary.name == "Updated"
|
||||
87
tests/test_model_definition/test_server_default.py
Normal file
87
tests/test_model_definition/test_server_default.py
Normal file
@ -0,0 +1,87 @@
|
||||
import asyncio
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from sqlalchemy import func, text
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Product(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "product"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
company: str = ormar.String(max_length=200, server_default="Acme")
|
||||
sort_order: int = ormar.Integer(server_default=text("10"))
|
||||
created: datetime = ormar.DateTime(server_default=func.now())
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def event_loop():
|
||||
loop = asyncio.get_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="module")
|
||||
async def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
def test_table_defined_properly():
|
||||
assert Product.Meta.model_fields["created"].nullable
|
||||
assert not Product.__fields__["created"].required
|
||||
assert Product.Meta.table.columns["created"].server_default.arg.name == "now"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_creation():
|
||||
async with database:
|
||||
async with database.transaction(force_rollback=True):
|
||||
p1 = Product(name="Test")
|
||||
assert p1.created is None
|
||||
await p1.save()
|
||||
await p1.load()
|
||||
assert p1.created is not None
|
||||
assert p1.company == "Acme"
|
||||
assert p1.sort_order == 10
|
||||
|
||||
date = datetime.strptime("2020-10-27 11:30", "%Y-%m-%d %H:%M")
|
||||
p3 = await Product.objects.create(
|
||||
name="Test2", created=date, company="Roadrunner", sort_order=1
|
||||
)
|
||||
assert p3.created is not None
|
||||
assert p3.created == date
|
||||
assert p1.created != p3.created
|
||||
assert p3.company == "Roadrunner"
|
||||
assert p3.sort_order == 1
|
||||
|
||||
p3 = await Product.objects.get(name="Test2")
|
||||
assert p3.company == "Roadrunner"
|
||||
assert p3.sort_order == 1
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
p2 = await Product.objects.create(name="Test3")
|
||||
assert p2.created is not None
|
||||
assert p2.company == "Acme"
|
||||
assert p2.sort_order == 10
|
||||
|
||||
if Product.db_backend_name() != "postgresql":
|
||||
# postgres use transaction timestamp so it will remain the same
|
||||
assert p1.created != p2.created # pragma nocover
|
||||
Reference in New Issue
Block a user