File: test_models.py

package info (click to toggle)
ormar 0.21.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,856 kB
  • sloc: python: 23,666; makefile: 34; sh: 14
file content (587 lines) | stat: -rw-r--r-- 20,897 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
import asyncio
import base64
import datetime
import os
import uuid
from enum import Enum

import ormar
import pydantic
import pytest
import sqlalchemy
from ormar.exceptions import ModelError, NoMatch, QueryDefinitionError

from tests.lifespan import init_tests
from tests.settings import create_config

base_ormar_config = create_config()


class JsonSample(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="jsons")

    id: int = ormar.Integer(primary_key=True)
    test_json = ormar.JSON(nullable=True)


blob = b"test"
blob2 = b"test2icac89uc98"


class LargeBinarySample(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="my_bolbs")

    id: int = ormar.Integer(primary_key=True)
    test_binary: bytes = ormar.LargeBinary(max_length=100000)


blob3 = os.urandom(64)
blob4 = os.urandom(100)


class LargeBinaryStr(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="my_str_bolbs")

    id: int = ormar.Integer(primary_key=True)
    test_binary: str = ormar.LargeBinary(
        max_length=100000, represent_as_base64_str=True
    )


class LargeBinaryNullableStr(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="my_str_bolbs2")

    id: int = ormar.Integer(primary_key=True)
    test_binary: str = ormar.LargeBinary(
        max_length=100000,
        represent_as_base64_str=True,
        nullable=True,
    )


class UUIDSample(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="uuids")

    id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4)
    test_text: str = ormar.Text()


class UUIDSample2(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="uuids2")

    id: uuid.UUID = ormar.UUID(
        primary_key=True, default=uuid.uuid4, uuid_format="string"
    )
    test_text: str = ormar.Text()


class User(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="users")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100, default="")


class User2(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="users2")

    id: str = ormar.String(primary_key=True, max_length=100)
    name: str = ormar.String(max_length=100, default="")


class Product(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="product")

    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.date.today)


class CountryNameEnum(Enum):
    CANADA = "Canada"
    ALGERIA = "Algeria"
    USA = "United States"
    BELIZE = "Belize"


class CountryCodeEnum(int, Enum):
    MINUS_TEN = -10
    ONE = 1
    TWO_HUNDRED_THIRTEEN = 213
    THOUSAND_TWO_HUNDRED = 1200


class Country(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="country")

    id: int = ormar.Integer(primary_key=True)
    name: CountryNameEnum = ormar.Enum(enum_class=CountryNameEnum, default="Canada")
    taxed: bool = ormar.Boolean(default=True)
    country_code: int = ormar.Enum(enum_class=CountryCodeEnum, default=1)


class NullableCountry(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="country2")

    id: int = ormar.Integer(primary_key=True)
    name: CountryNameEnum = ormar.Enum(enum_class=CountryNameEnum, nullable=True)


class NotNullableCountry(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="country3")

    id: int = ormar.Integer(primary_key=True)
    name: CountryNameEnum = ormar.Enum(enum_class=CountryNameEnum, nullable=False)


create_test_database = init_tests(base_ormar_config)


def test_model_class():
    assert list(User.ormar_config.model_fields.keys()) == ["id", "name"]
    assert issubclass(
        User.ormar_config.model_fields["id"].__class__, pydantic.fields.FieldInfo
    )
    assert User.ormar_config.model_fields["id"].primary_key is True
    assert isinstance(User.ormar_config.model_fields["name"], pydantic.fields.FieldInfo)
    assert User.ormar_config.model_fields["name"].max_length == 100
    assert isinstance(User.ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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)

            items[0].test_json = "[1, 2, 3]"
            assert items[0].test_json == [1, 2, 3]


@pytest.mark.asyncio
async def test_binary_column():
    async with base_ormar_config.database:
        async with base_ormar_config.database.transaction(force_rollback=True):
            await LargeBinarySample.objects.create(test_binary=blob)
            await LargeBinarySample.objects.create(test_binary=blob2)

            items = await LargeBinarySample.objects.all()
            assert len(items) == 2
            assert items[0].test_binary == blob
            assert items[1].test_binary == blob2

            items[0].test_binary = "test2icac89uc98"
            assert items[0].test_binary == b"test2icac89uc98"


@pytest.mark.asyncio
async def test_binary_str_column():
    async with base_ormar_config.database:
        async with base_ormar_config.database.transaction(force_rollback=True):
            await LargeBinaryStr(test_binary=blob3).save()
            await LargeBinaryStr.objects.create(test_binary=blob4)

            items = await LargeBinaryStr.objects.all()
            assert len(items) == 2
            assert items[0].test_binary == base64.b64encode(blob3).decode()
            items[0].test_binary = base64.b64encode(blob4).decode()
            assert items[0].test_binary == base64.b64encode(blob4).decode()
            assert items[1].test_binary == base64.b64encode(blob4).decode()
            assert items[1].__dict__["test_binary"] == blob4


@pytest.mark.asyncio
async def test_binary_nullable_str_column():
    async with base_ormar_config.database:
        async with base_ormar_config.database.transaction(force_rollback=True):
            await LargeBinaryNullableStr().save()
            await LargeBinaryNullableStr.objects.create()
            items = await LargeBinaryNullableStr.objects.all()
            assert len(items) == 2

            items[0].test_binary = blob3
            items[1].test_binary = blob4

            await LargeBinaryNullableStr.objects.bulk_update(items)
            items = await LargeBinaryNullableStr.objects.all()
            assert len(items) == 2
            assert items[0].test_binary == base64.b64encode(blob3).decode()
            items[0].test_binary = base64.b64encode(blob4).decode()
            assert items[0].test_binary == base64.b64encode(blob4).decode()
            assert items[1].test_binary == base64.b64encode(blob4).decode()
            assert items[1].__dict__["test_binary"] == blob4

            await LargeBinaryNullableStr.objects.bulk_create(
                [LargeBinaryNullableStr(), LargeBinaryNullableStr(test_binary=blob3)]
            )
            items = await LargeBinaryNullableStr.objects.all()
            assert len(items) == 4
            await items[0].update(test_binary=blob4)
            check_item = await LargeBinaryNullableStr.objects.get(id=items[0].id)
            assert check_item.test_binary == base64.b64encode(blob4).decode()


@pytest.mark.asyncio
async def test_uuid_column():
    async with base_ormar_config.database:
        async with base_ormar_config.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.model_dump()).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 base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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

            user2 = 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=user2.id)
            assert same_user.id == user2.id
            assert same_user.pk == user2.pk

            assert await User.objects.order_by("-name").get() == user


@pytest.mark.asyncio
async def test_model_filter():
    async with base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.database.transaction(force_rollback=True):
            with pytest.raises(ormar.NoMatch):
                await User.objects.first()

            assert await User.objects.first_or_none() is None

            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()

            assert await User.objects.first_or_none(name="Lucy") is None
            assert await User.objects.filter(name="Lucy").first_or_none() is None

            assert await User.objects.order_by("name").first() == jane


@pytest.mark.asyncio
async def test_model_choices():
    """Test that enum work properly for various types of fields."""
    async with base_ormar_config.database:
        # Test valid enums values.
        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
            await Country.objects.create(
                name=name, taxed=taxed, country_code=country_code
            )

        with pytest.raises(ValueError):
            name, taxed, country_code = "Algeria", True, 967
            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
            country = Country()
            country.country_code = country_code

        with pytest.raises(ValueError):
            name, taxed, country_code = "Saudi Arabia", True, 1
            country = Country()
            country.name = name

        # check also update from queryset
        with pytest.raises(ValueError):
            await Country(name="Belize").save()
            await Country.objects.filter(name="Belize").update(name="Vietnam")


@pytest.mark.asyncio
async def test_nullable_field_model_enum():
    """Test that enum work properly for according to nullable setting"""
    async with base_ormar_config.database:
        c1 = await NullableCountry(name=None).save()
        assert c1.name is None

        with pytest.raises(ValueError):
            await NotNullableCountry(name=None).save()


@pytest.mark.asyncio
async def test_start_and_end_filters():
    async with base_ormar_config.database:
        async with base_ormar_config.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 base_ormar_config.database:
        async with base_ormar_config.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"


def test_constraints():
    with pytest.raises(pydantic.ValidationError) as e:
        Product(name="T-Shirt", rating=50, in_stock=True)
    assert "Input should be less than or equal to 5 " in str(e.value)