version with pydantic inheritance passing all the tests
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
3
tests/settings.py
Normal file
3
tests/settings.py
Normal file
@ -0,0 +1,3 @@
|
||||
import os
|
||||
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///test.db")
|
||||
60
tests/test_columns.py
Normal file
60
tests/test_columns.py
Normal file
@ -0,0 +1,60 @@
|
||||
import datetime
|
||||
|
||||
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 time():
|
||||
return datetime.datetime.now().time()
|
||||
|
||||
|
||||
class Example(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "example"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=200, default='aaa')
|
||||
created: ormar.DateTime(default=datetime.datetime.now)
|
||||
created_day: ormar.Date(default=datetime.date.today)
|
||||
created_time: ormar.Time(default=time)
|
||||
description: ormar.Text(nullable=True)
|
||||
value: ormar.Float(nullable=True)
|
||||
data: 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()
|
||||
55
tests/test_fastapi_usage.py
Normal file
55
tests/test_fastapi_usage.py
Normal file
@ -0,0 +1,55 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
category: ormar.ForeignKey(Category, nullable=True)
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item)
|
||||
async def create_item(item: Item):
|
||||
return item
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_read_main():
|
||||
response = client.post(
|
||||
"/items/", json={"name": "test", "id": 1, "category": {"name": "test cat"}}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"category": {"id": None, "name": "test cat"},
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
}
|
||||
item = Item(**response.json())
|
||||
assert item.id == 1
|
||||
296
tests/test_foreign_keys.py
Normal file
296
tests/test_foreign_keys.py
Normal file
@ -0,0 +1,296 @@
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from pydantic import ValidationError
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import NoMatch, MultipleMatches, RelationshipInstanceError
|
||||
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: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
album: ormar.ForeignKey(Album)
|
||||
title: ormar.String(max_length=100)
|
||||
position: ormar.Integer()
|
||||
|
||||
|
||||
class Cover(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "covers"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
album: ormar.ForeignKey(Album, related_name="cover_pictures")
|
||||
title: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Organisation(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "org"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
ident: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Team(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "teams"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
org: ormar.ForeignKey(Organisation)
|
||||
name: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Member(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "members"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
team: ormar.ForeignKey(Team)
|
||||
email: ormar.String(max_length=100)
|
||||
|
||||
|
||||
@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_wrong_query_foreign_key_type():
|
||||
with pytest.raises(RelationshipInstanceError):
|
||||
Track(title="The Error", album="wrong_pk_type")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setting_explicitly_empty_relation():
|
||||
async with database:
|
||||
track = Track(album=None, title="The Bird", position=1)
|
||||
assert track.album is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_related_name():
|
||||
async with database:
|
||||
album = await Album.objects.create(name="Vanilla")
|
||||
await Cover.objects.create(album=album, title="The cover file")
|
||||
|
||||
assert len(album.cover_pictures) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_crud():
|
||||
async with database:
|
||||
album = Album(name="Malibu")
|
||||
await album.save()
|
||||
track1 = Track(album=album, title="The Bird", position=1)
|
||||
track2 = Track(album=album, title="Heart don't stand a chance", position=2)
|
||||
track3 = Track(album=album, title="The Waters", position=3)
|
||||
await track1.save()
|
||||
await track2.save()
|
||||
await track3.save()
|
||||
|
||||
assert len(album.tracks) == 3
|
||||
assert album.tracks[1].title == "Heart don't stand a chance"
|
||||
|
||||
track = await Track.objects.get(title="The Bird")
|
||||
assert track.album.pk == album.pk
|
||||
assert track.album.name is None
|
||||
await track.album.load()
|
||||
assert track.album.name == "Malibu"
|
||||
|
||||
|
||||
album1 = await Album.objects.get(name="Malibu")
|
||||
assert album1.pk == 1
|
||||
assert album1.tracks is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_select_related():
|
||||
async with database:
|
||||
album = Album(name="Malibu")
|
||||
await album.save()
|
||||
track1 = Track(album=album, title="The Bird", position=1)
|
||||
track2 = Track(album=album, title="Heart don't stand a chance", position=2)
|
||||
track3 = Track(album=album, title="The Waters", position=3)
|
||||
await track1.save()
|
||||
await track2.save()
|
||||
await track3.save()
|
||||
|
||||
fantasies = Album(name="Fantasies")
|
||||
await fantasies.save()
|
||||
track4 = Track(album=fantasies, title="Help I'm Alive", position=1)
|
||||
track5 = Track(album=fantasies, title="Sick Muse", position=2)
|
||||
track6 = Track(album=fantasies, title="Satellite Mind", position=3)
|
||||
await track4.save()
|
||||
await track5.save()
|
||||
await track6.save()
|
||||
|
||||
track = await Track.objects.select_related("album").get(title="The Bird")
|
||||
assert track.album.name == "Malibu"
|
||||
|
||||
tracks = await Track.objects.select_related("album").all()
|
||||
assert len(tracks) == 6
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fk_filter():
|
||||
async with database:
|
||||
malibu = Album(name="Malibu%")
|
||||
await malibu.save()
|
||||
await Track.objects.create(album=malibu, title="The Bird", position=1)
|
||||
await Track.objects.create(
|
||||
album=malibu, title="Heart don't stand a chance", position=2
|
||||
)
|
||||
await Track.objects.create(album=malibu, title="The Waters", position=3)
|
||||
|
||||
fantasies = await Album.objects.create(name="Fantasies")
|
||||
await Track.objects.create(album=fantasies, title="Help I'm Alive", position=1)
|
||||
await Track.objects.create(album=fantasies, title="Sick Muse", position=2)
|
||||
await Track.objects.create(album=fantasies, title="Satellite Mind", position=3)
|
||||
|
||||
tracks = (
|
||||
await Track.objects.select_related("album")
|
||||
.filter(album__name="Fantasies")
|
||||
.all()
|
||||
)
|
||||
assert len(tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.album.name == "Fantasies"
|
||||
|
||||
tracks = (
|
||||
await Track.objects.select_related("album")
|
||||
.filter(album__name__icontains="fan")
|
||||
.all()
|
||||
)
|
||||
assert len(tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.album.name == "Fantasies"
|
||||
|
||||
tracks = await Track.objects.filter(album__name__contains="fan").all()
|
||||
assert len(tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.album.name == "Fantasies"
|
||||
|
||||
tracks = await Track.objects.filter(album__name__contains="Malibu%").all()
|
||||
assert len(tracks) == 3
|
||||
|
||||
tracks = await Track.objects.filter(album=malibu).select_related("album").all()
|
||||
assert len(tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.album.name == "Malibu%"
|
||||
|
||||
tracks = await Track.objects.select_related("album").all(album=malibu)
|
||||
assert len(tracks) == 3
|
||||
for track in tracks:
|
||||
assert track.album.name == "Malibu%"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_fk():
|
||||
async with database:
|
||||
acme = await Organisation.objects.create(ident="ACME Ltd")
|
||||
red_team = await Team.objects.create(org=acme, name="Red Team")
|
||||
blue_team = await Team.objects.create(org=acme, name="Blue Team")
|
||||
await Member.objects.create(team=red_team, email="a@example.org")
|
||||
await Member.objects.create(team=red_team, email="b@example.org")
|
||||
await Member.objects.create(team=blue_team, email="c@example.org")
|
||||
await Member.objects.create(team=blue_team, email="d@example.org")
|
||||
|
||||
other = await Organisation.objects.create(ident="Other ltd")
|
||||
team = await Team.objects.create(org=other, name="Green Team")
|
||||
await Member.objects.create(team=team, email="e@example.org")
|
||||
|
||||
members = (
|
||||
await Member.objects.select_related("team__org")
|
||||
.filter(team__org__ident="ACME Ltd")
|
||||
.all()
|
||||
)
|
||||
assert len(members) == 4
|
||||
for member in members:
|
||||
assert member.team.org.ident == "ACME Ltd"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pk_filter():
|
||||
async with database:
|
||||
fantasies = await Album.objects.create(name="Test")
|
||||
await Track.objects.create(album=fantasies, title="Test1", position=1)
|
||||
await Track.objects.create(album=fantasies, title="Test2", position=2)
|
||||
await Track.objects.create(album=fantasies, title="Test3", position=3)
|
||||
tracks = await Track.objects.select_related("album").filter(pk=1).all()
|
||||
assert len(tracks) == 1
|
||||
|
||||
tracks = (
|
||||
await Track.objects.select_related("album")
|
||||
.filter(position=2, album__name="Test")
|
||||
.all()
|
||||
)
|
||||
assert len(tracks) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_limit_and_offset():
|
||||
async with database:
|
||||
fantasies = await Album.objects.create(name="Limitless")
|
||||
await Track.objects.create(id=None, album=fantasies, title="Sample", position=1)
|
||||
await Track.objects.create(album=fantasies, title="Sample2", position=2)
|
||||
await Track.objects.create(album=fantasies, title="Sample3", position=3)
|
||||
|
||||
tracks = await Track.objects.limit(1).all()
|
||||
assert len(tracks) == 1
|
||||
assert tracks[0].title == "Sample"
|
||||
|
||||
tracks = await Track.objects.limit(1).offset(1).all()
|
||||
assert len(tracks) == 1
|
||||
assert tracks[0].title == "Sample2"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_exceptions():
|
||||
async with database:
|
||||
fantasies = await Album.objects.create(name="Test")
|
||||
|
||||
with pytest.raises(NoMatch):
|
||||
await Album.objects.get(name="Test2")
|
||||
|
||||
await Track.objects.create(album=fantasies, title="Test1", position=1)
|
||||
await Track.objects.create(album=fantasies, title="Test2", position=2)
|
||||
await Track.objects.create(album=fantasies, title="Test3", position=3)
|
||||
with pytest.raises(MultipleMatches):
|
||||
await Track.objects.select_related("album").get(album=fantasies)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrong_model_passed_as_fk():
|
||||
with pytest.raises(RelationshipInstanceError):
|
||||
org = await Organisation.objects.create(ident="ACME Ltd")
|
||||
await Track.objects.create(album=org, title="Test1", position=1)
|
||||
160
tests/test_model_definition.py
Normal file
160
tests/test_model_definition.py
Normal file
@ -0,0 +1,160 @@
|
||||
import datetime
|
||||
import decimal
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar.fields as fields
|
||||
from ormar.exceptions import ModelDefinitionError
|
||||
from ormar.models import Model
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class ExampleModel(Model):
|
||||
class Meta:
|
||||
tablename = "example"
|
||||
metadata = metadata
|
||||
|
||||
test: fields.Integer(primary_key=True)
|
||||
test_string: fields.String(max_length=250)
|
||||
test_text: fields.Text(default="")
|
||||
test_bool: fields.Boolean(nullable=False)
|
||||
test_float: fields.Float() = None
|
||||
test_datetime: fields.DateTime(default=datetime.datetime.now)
|
||||
test_date: fields.Date(default=datetime.date.today)
|
||||
test_time: fields.Time(default=datetime.time)
|
||||
test_json: fields.JSON(default={})
|
||||
test_bigint: fields.BigInteger(default=0)
|
||||
test_decimal: fields.Decimal(scale=10, precision=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 = "example2"
|
||||
metadata = metadata
|
||||
|
||||
test: fields.Integer(primary_key=True)
|
||||
test_string: fields.String(max_length=250)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def example():
|
||||
return ExampleModel(pk=1, test_string="test", test_bool=True, test_decimal=decimal.Decimal(3.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 == {}
|
||||
|
||||
example.test = 12
|
||||
assert example.test == 12
|
||||
|
||||
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])
|
||||
|
||||
|
||||
def test_no_pk_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example3"
|
||||
metadata = metadata
|
||||
|
||||
test_string: fields.String(max_length=250)
|
||||
|
||||
|
||||
def test_two_pks_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example3"
|
||||
metadata = metadata
|
||||
|
||||
id: fields.Integer(primary_key=True)
|
||||
test_string: fields.String(max_length=250, primary_key=True)
|
||||
|
||||
|
||||
def test_setting_pk_column_as_pydantic_only_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example4"
|
||||
metadata = metadata
|
||||
|
||||
test: fields.Integer(primary_key=True, pydantic_only=True)
|
||||
|
||||
|
||||
def test_decimal_error_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example5"
|
||||
metadata = metadata
|
||||
|
||||
test: fields.Decimal(primary_key=True)
|
||||
|
||||
|
||||
def test_string_error_in_model_definition():
|
||||
with pytest.raises(ModelDefinitionError):
|
||||
class ExampleModel2(Model):
|
||||
class Meta:
|
||||
tablename = "example6"
|
||||
metadata = metadata
|
||||
|
||||
test: fields.String(primary_key=True)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
211
tests/test_models.py
Normal file
211
tests/test_models.py
Normal file
@ -0,0 +1,211 @@
|
||||
import databases
|
||||
import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import QueryDefinitionError
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "users"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100, default='')
|
||||
|
||||
|
||||
class Product(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "product"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
rating: ormar.Integer(minimum=1, maximum=5)
|
||||
in_stock: ormar.Boolean(default=False)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
def test_model_class():
|
||||
assert list(User.Meta.model_fields.keys()) == ["id", "name"]
|
||||
assert issubclass(User.Meta.model_fields["id"], pydantic.ConstrainedInt)
|
||||
assert User.Meta.model_fields["id"].primary_key is True
|
||||
assert issubclass(User.Meta.model_fields["name"], pydantic.ConstrainedStr)
|
||||
assert User.Meta.model_fields["name"].max_length == 100
|
||||
assert isinstance(User.Meta.table, sqlalchemy.Table)
|
||||
|
||||
|
||||
def test_model_pk():
|
||||
user = User(pk=1)
|
||||
assert user.pk == 1
|
||||
assert user.id == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_crud():
|
||||
async with database:
|
||||
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:
|
||||
with pytest.raises(ormar.NoMatch):
|
||||
await User.objects.get()
|
||||
|
||||
user = await User.objects.create(name="Tom")
|
||||
lookup = await User.objects.get()
|
||||
assert lookup == user
|
||||
|
||||
user = await User.objects.create(name="Jane")
|
||||
with pytest.raises(ormar.MultipleMatches):
|
||||
await User.objects.get()
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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():
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
assert await User.objects.filter(name="Lucy").first() is None
|
||||
118
tests/test_more_reallife_fastapi.py
Normal file
118
tests/test_more_reallife_fastapi.py
Normal file
@ -0,0 +1,118 @@
|
||||
from typing import List
|
||||
|
||||
import databases
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
app = FastAPI()
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
app.state.database = database
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup() -> None:
|
||||
database_ = app.state.database
|
||||
if not database_.is_connected:
|
||||
await database_.connect()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown() -> None:
|
||||
database_ = app.state.database
|
||||
if database_.is_connected:
|
||||
await database_.disconnect()
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
category: ormar.ForeignKey(Category, nullable=True)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@app.get("/items/", response_model=List[Item])
|
||||
async def get_items():
|
||||
items = await Item.objects.select_related("category").all()
|
||||
return [item.dict() for item in items]
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item)
|
||||
async def create_item(item: Item):
|
||||
item = await Item.objects.create(**item.dict())
|
||||
return item.dict()
|
||||
|
||||
|
||||
@app.post("/categories/", response_model=Category)
|
||||
async def create_category(category: Category):
|
||||
await category.save()
|
||||
return category
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
async def get_item(item_id: int, item: Item):
|
||||
item_db = await Item.objects.get(pk=item_id)
|
||||
return {"updated_rows": await item_db.update(**item.dict())}
|
||||
|
||||
|
||||
@app.delete("/items/{item_id}")
|
||||
async def delete_item(item_id: int, item: Item):
|
||||
item_db = await Item.objects.get(pk=item_id)
|
||||
return {"deleted_rows": await item_db.delete()}
|
||||
|
||||
|
||||
def test_all_endpoints():
|
||||
client = TestClient(app)
|
||||
with client as client:
|
||||
response = client.post("/categories/", json={"name": "test cat"})
|
||||
category = response.json()
|
||||
response = client.post(
|
||||
"/items/", json={"name": "test", "id": 1, "category": category}
|
||||
)
|
||||
item = Item(**response.json())
|
||||
assert item.pk is not None
|
||||
|
||||
response = client.get("/items/")
|
||||
items = [Item(**item) for item in response.json()]
|
||||
assert items[0] == item
|
||||
|
||||
item.name = "New name"
|
||||
response = client.put(f"/items/{item.pk}", json=item.dict())
|
||||
assert response.json().get("updated_rows") == 1
|
||||
|
||||
response = client.get("/items/")
|
||||
items = [Item(**item) for item in response.json()]
|
||||
assert items[0].name == "New name"
|
||||
|
||||
response = client.delete(f"/items/{item.pk}", json=item.dict())
|
||||
assert response.json().get("deleted_rows") == 1
|
||||
response = client.get("/items/")
|
||||
items = response.json()
|
||||
assert len(items) == 0
|
||||
133
tests/test_same_table_joins.py
Normal file
133
tests/test_same_table_joins.py
Normal file
@ -0,0 +1,133 @@
|
||||
import asyncio
|
||||
|
||||
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 Department(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "departments"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True, autoincrement=False)
|
||||
name: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class SchoolClass(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "schoolclasses"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
department: ormar.ForeignKey(Department, nullable=False)
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "students"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
schoolclass: ormar.ForeignKey(SchoolClass)
|
||||
category: ormar.ForeignKey(Category, nullable=True)
|
||||
|
||||
|
||||
class Teacher(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "teachers"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: ormar.Integer(primary_key=True)
|
||||
name: ormar.String(max_length=100)
|
||||
schoolclass: ormar.ForeignKey(SchoolClass)
|
||||
category: ormar.ForeignKey(Category, nullable=True)
|
||||
|
||||
|
||||
@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)
|
||||
department = await Department.objects.create(id=1, name="Math Department")
|
||||
class1 = await SchoolClass.objects.create(name="Math", department=department)
|
||||
category = await Category.objects.create(name="Foreign")
|
||||
category2 = await Category.objects.create(name="Domestic")
|
||||
await Student.objects.create(name="Jane", category=category, schoolclass=class1)
|
||||
await Student.objects.create(name="Jack", category=category2, schoolclass=class1)
|
||||
await Teacher.objects.create(name="Joe", category=category2, schoolclass=class1)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_multiple_instances_of_same_table_in_schema():
|
||||
async with database:
|
||||
classes = await SchoolClass.objects.select_related(
|
||||
["teachers__category", "students"]
|
||||
).all()
|
||||
assert classes[0].name == "Math"
|
||||
assert classes[0].students[0].name == "Jane"
|
||||
|
||||
assert len(classes[0].dict().get("students")) == 2
|
||||
|
||||
# related fields of main model are only populated by pk
|
||||
# unless there is a required foreign key somewhere along the way
|
||||
# since department is required for schoolclass it was pre loaded (again)
|
||||
# but you can load them anytime
|
||||
assert classes[0].students[0].schoolclass.name == "Math"
|
||||
assert classes[0].students[0].schoolclass.department.name is None
|
||||
await classes[0].students[0].schoolclass.department.load()
|
||||
assert classes[0].students[0].schoolclass.department.name == "Math Department"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_right_tables_join():
|
||||
async with database:
|
||||
classes = await SchoolClass.objects.select_related(
|
||||
["teachers__category", "students"]
|
||||
).all()
|
||||
assert classes[0].teachers[0].category.name == "Domestic"
|
||||
|
||||
assert classes[0].students[0].category.name is None
|
||||
await classes[0].students[0].category.load()
|
||||
assert classes[0].students[0].category.name == "Foreign"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_reverse_related_objects():
|
||||
async with database:
|
||||
classes = await SchoolClass.objects.select_related(
|
||||
["teachers__category", "students__category"]
|
||||
).all()
|
||||
assert classes[0].name == "Math"
|
||||
assert classes[0].students[1].name == "Jack"
|
||||
assert classes[0].teachers[0].category.name == "Domestic"
|
||||
Reference in New Issue
Block a user