Merge pull request #28 from collerek/test_field_infos
Change model definition notation to support type hints and static checks
This commit is contained in:
@ -27,7 +27,7 @@ script:
|
|||||||
- DATABASE_URL=postgresql://localhost/test_database scripts/test.sh
|
- DATABASE_URL=postgresql://localhost/test_database scripts/test.sh
|
||||||
- DATABASE_URL=mysql://localhost/test_database scripts/test.sh
|
- DATABASE_URL=mysql://localhost/test_database scripts/test.sh
|
||||||
- DATABASE_URL=sqlite:///test.db scripts/test.sh
|
- DATABASE_URL=sqlite:///test.db scripts/test.sh
|
||||||
- mypy --config-file mypy.ini ormar
|
- mypy --config-file mypy.ini ormar tests
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
- codecov
|
- codecov
|
||||||
12
README.md
12
README.md
@ -75,8 +75,8 @@ class Album(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(length=100)
|
name: str = ormar.String(length=100)
|
||||||
|
|
||||||
|
|
||||||
class Track(ormar.Model):
|
class Track(ormar.Model):
|
||||||
@ -85,10 +85,10 @@ class Track(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
album: ormar.ForeignKey(Album)
|
album= ormar.ForeignKey(Album)
|
||||||
title: ormar.String(length=100)
|
title: str = ormar.String(length=100)
|
||||||
position: ormar.Integer()
|
position: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
# Create some records to work with.
|
# Create some records to work with.
|
||||||
|
|||||||
@ -44,9 +44,9 @@ git checkout -b my-new-feature-branch
|
|||||||
# 5. Formatting and linting
|
# 5. Formatting and linting
|
||||||
# ormar uses black for formatting, flake8 for linting and mypy for type hints check
|
# ormar uses black for formatting, flake8 for linting and mypy for type hints check
|
||||||
# run all of the following as all those calls will be run on travis after every push
|
# run all of the following as all those calls will be run on travis after every push
|
||||||
black ormar
|
black ormar tests
|
||||||
flake8 ormar
|
flake8 ormar
|
||||||
mypy --config-file mypy.ini ormar
|
mypy --config-file mypy.ini ormar tests
|
||||||
|
|
||||||
# 6. Run tests
|
# 6. Run tests
|
||||||
# on localhost all tests are run against sglite backend
|
# on localhost all tests are run against sglite backend
|
||||||
|
|||||||
@ -78,7 +78,7 @@ Used in sql only.
|
|||||||
|
|
||||||
Sample usage:
|
Sample usage:
|
||||||
|
|
||||||
```Python hl_lines="19-21"
|
```Python hl_lines="21-23"
|
||||||
--8<-- "../docs_src/fields/docs004.py"
|
--8<-- "../docs_src/fields/docs004.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -75,8 +75,11 @@ class Album(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
# note that type hints are optional so
|
||||||
name: ormar.String(length=100)
|
# id = ormar.Integer(primary_key=True)
|
||||||
|
# is also valid
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(length=100)
|
||||||
|
|
||||||
|
|
||||||
class Track(ormar.Model):
|
class Track(ormar.Model):
|
||||||
@ -85,10 +88,10 @@ class Track(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
album: ormar.ForeignKey(Album)
|
album: Optional[Album] =ormar.ForeignKey(Album)
|
||||||
title: ormar.String(length=100)
|
title: str = ormar.String(length=100)
|
||||||
position: ormar.Integer()
|
position: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
# Create some records to work with.
|
# Create some records to work with.
|
||||||
|
|||||||
@ -34,7 +34,7 @@ By default if you assign primary key to `Integer` field, the `autoincrement` opt
|
|||||||
You can disable by passing `autoincremant=False`.
|
You can disable by passing `autoincremant=False`.
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
id: ormar.Integer(primary_key=True, autoincrement=False)
|
id: int = ormar.Integer(primary_key=True, autoincrement=False)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fields names vs Column names
|
### Fields names vs Column names
|
||||||
@ -50,14 +50,44 @@ Here you have a sample model with changed names
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note that you can also change the ForeignKey column name
|
Note that you can also change the ForeignKey column name
|
||||||
```Python hl_lines="9"
|
```Python hl_lines="21"
|
||||||
--8<-- "../docs_src/models/docs009.py"
|
--8<-- "../docs_src/models/docs009.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
But for now you cannot change the ManyToMany column names as they go through other Model anyway.
|
But for now you cannot change the ManyToMany column names as they go through other Model anyway.
|
||||||
```Python hl_lines="18"
|
```Python hl_lines="28"
|
||||||
--8<-- "../docs_src/models/docs010.py"
|
--8<-- "../docs_src/models/docs010.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Type Hints & Legacy
|
||||||
|
|
||||||
|
Before version 0.4.0 `ormar` supported only one way of defining `Fields` on a `Model` using python type hints as pydantic.
|
||||||
|
|
||||||
|
```Python hl_lines="15-17"
|
||||||
|
--8<-- "../docs_src/models/docs011.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
But that didn't play well with static type checkers like `mypy` and `pydantic` PyCharm plugin.
|
||||||
|
|
||||||
|
Therefore from version >=0.4.0 `ormar` switched to new notation.
|
||||||
|
|
||||||
|
```Python hl_lines="15-17"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that type hints are **optional** so perfectly valid `ormar` code can look like this:
|
||||||
|
|
||||||
|
```Python hl_lines="15-17"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
Even if you use type hints **`ormar` does not use them to construct `pydantic` fields!**
|
||||||
|
|
||||||
|
Type hints are there only to support static checkers and linting,
|
||||||
|
`ormar` construct annotations used by `pydantic` from own fields.
|
||||||
|
|
||||||
|
|
||||||
### Database initialization/ migrations
|
### Database initialization/ migrations
|
||||||
|
|
||||||
Note that all examples assume that you already have a database.
|
Note that all examples assume that you already have a database.
|
||||||
@ -133,6 +163,20 @@ Created instance needs to be passed to every `Model` with `Meta` class `metadata
|
|||||||
You need to create the `MetaData` instance **only once** and use it for all models.
|
You need to create the `MetaData` instance **only once** and use it for all models.
|
||||||
You can create several ones if you want to use multiple databases.
|
You can create several ones if you want to use multiple databases.
|
||||||
|
|
||||||
|
#### Best practice
|
||||||
|
|
||||||
|
Only thing that `ormar` expects is a class with name `Meta` and two class variables: `metadata` and `databases`.
|
||||||
|
|
||||||
|
So instead of providing the same parameters over and over again for all models you should creata a class and subclass it in all models.
|
||||||
|
|
||||||
|
```Python hl_lines="14 20 33"
|
||||||
|
--8<-- "../docs_src/models/docs013.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
You need to subclass your `MainMeta` class in each `Model` class as those classes store configuration variables
|
||||||
|
that otherwise would be overwritten by each `Model`.
|
||||||
|
|
||||||
### Table Names
|
### Table Names
|
||||||
|
|
||||||
By default table name is created from Model class name as lowercase name plus 's'.
|
By default table name is created from Model class name as lowercase name plus 's'.
|
||||||
@ -278,7 +322,7 @@ To access ormar `Fields` you can use `Model.Meta.model_fields` parameter
|
|||||||
|
|
||||||
For example to list table model fields you can:
|
For example to list table model fields you can:
|
||||||
|
|
||||||
```Python hl_lines="19"
|
```Python hl_lines="20"
|
||||||
--8<-- "../docs_src/models/docs005.py"
|
--8<-- "../docs_src/models/docs005.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
28
docs/mypy.md
Normal file
28
docs/mypy.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
To provide better errors check you should use mypy with pydantic [plugin][plugin]
|
||||||
|
|
||||||
|
Note that legacy model declaration type will raise static type analyzers errors.
|
||||||
|
|
||||||
|
So you **cannot use the old notation** like this:
|
||||||
|
|
||||||
|
```Python hl_lines="15-17"
|
||||||
|
--8<-- "../docs_src/models/docs011.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead switch to notation introduced in version 0.4.0.
|
||||||
|
|
||||||
|
```Python hl_lines="15-17"
|
||||||
|
--8<-- "../docs_src/models/docs012.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that above example is not using the type hints, so further operations with mypy might fail, depending on the context.
|
||||||
|
|
||||||
|
Preferred notation should look liked this:
|
||||||
|
|
||||||
|
```Python hl_lines="15-17"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[plugin]: https://pydantic-docs.helpmanual.io/mypy_plugin/
|
||||||
18
docs/plugin.md
Normal file
18
docs/plugin.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
While `ormar` will work with any IDE there is a PyCharm `pydantic` plugin that enhances the user experience for this IDE.
|
||||||
|
|
||||||
|
Plugin is available on the JetBrains Plugins Repository for PyCharm: [plugin page][plugin page].
|
||||||
|
|
||||||
|
You can install the plugin for free from the plugin marketplace
|
||||||
|
(PyCharm's Preferences -> Plugin -> Marketplace -> search "pydantic").
|
||||||
|
|
||||||
|
!!!note
|
||||||
|
For plugin to work properly you need to provide valid type hints for model fields.
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
Plugin supports type hints, argument inspection and more but mainly only for __init__ methods
|
||||||
|
|
||||||
|
More information can be found on the
|
||||||
|
[official plugin page](https://plugins.jetbrains.com/plugin/12861-pydantic)
|
||||||
|
and [github repository](https://github.com/koxudaxi/pydantic-pycharm-plugin).
|
||||||
|
|
||||||
|
[plugin page]: https://plugins.jetbrains.com/plugin/12861-pydantic
|
||||||
210
docs/queries.md
210
docs/queries.md
@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
## QuerySet
|
## QuerySet
|
||||||
|
|
||||||
Each Model is auto registered with a QuerySet that represents the underlaying query and it's options.
|
Each Model is auto registered with a `QuerySet` that represents the underlaying query and it's options.
|
||||||
|
|
||||||
Most of the methods are also available through many to many relation interface.
|
Most of the methods are also available through many to many relation interface.
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
To see which one are supported and how to construct relations visit [relations][relations].
|
||||||
|
|
||||||
Given the Models like this
|
Given the Models like this
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
@ -95,74 +98,24 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai
|
|||||||
|
|
||||||
Return number of rows updated.
|
Return number of rows updated.
|
||||||
|
|
||||||
```python hl_lines="24-28"
|
```Python hl_lines="26-28"
|
||||||
import databases
|
--8<-- "../docs_src/queries/docs002.py"
|
||||||
import ormar
|
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
database = databases.Database("sqlite:///db.sqlite")
|
|
||||||
metadata = sqlalchemy.MetaData()
|
|
||||||
|
|
||||||
class Book(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "books"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
|
||||||
title: ormar.String(max_length=200)
|
|
||||||
author: ormar.String(max_length=100)
|
|
||||||
genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy'])
|
|
||||||
|
|
||||||
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure')
|
|
||||||
await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction')
|
|
||||||
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction')
|
|
||||||
|
|
||||||
|
|
||||||
# queryset needs to be filtered before deleting to prevent accidental overwrite
|
|
||||||
# to update whole database table each=True needs to be provided as a safety switch
|
|
||||||
await Book.objects.update(each=True, genre='Fiction')
|
|
||||||
all_books = await Book.objects.filter(genre='Fiction').all()
|
|
||||||
assert len(all_books) == 3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
Queryset needs to be filtered before updating to prevent accidental overwrite.
|
||||||
|
|
||||||
|
To update whole database table `each=True` needs to be provided as a safety switch
|
||||||
|
|
||||||
|
|
||||||
### update_or_create
|
### update_or_create
|
||||||
|
|
||||||
`update_or_create(**kwargs) -> Model`
|
`update_or_create(**kwargs) -> Model`
|
||||||
|
|
||||||
Updates the model, or in case there is no match in database creates a new one.
|
Updates the model, or in case there is no match in database creates a new one.
|
||||||
|
|
||||||
```python hl_lines="24-30"
|
```Python hl_lines="26-32"
|
||||||
import databases
|
--8<-- "../docs_src/queries/docs003.py"
|
||||||
import ormar
|
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
database = databases.Database("sqlite:///db.sqlite")
|
|
||||||
metadata = sqlalchemy.MetaData()
|
|
||||||
|
|
||||||
class Book(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "books"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
|
||||||
title: ormar.String(max_length=200)
|
|
||||||
author: ormar.String(max_length=100)
|
|
||||||
genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy'])
|
|
||||||
|
|
||||||
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure')
|
|
||||||
await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction')
|
|
||||||
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction')
|
|
||||||
|
|
||||||
|
|
||||||
# if not exist the instance will be persisted in db
|
|
||||||
vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction')
|
|
||||||
assert await Book.objects.count() == 1
|
|
||||||
|
|
||||||
# if pk or pkname passed in kwargs (like id here) the object will be updated
|
|
||||||
assert await Book.objects.update_or_create(id=vol2.id, genre='Historic')
|
|
||||||
assert await Book.objects.count() == 1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
!!!note
|
!!!note
|
||||||
@ -177,36 +130,8 @@ Allows you to create multiple objects at once.
|
|||||||
|
|
||||||
A valid list of `Model` objects needs to be passed.
|
A valid list of `Model` objects needs to be passed.
|
||||||
|
|
||||||
```python hl_lines="20-26"
|
```python hl_lines="21-27"
|
||||||
import databases
|
--8<-- "../docs_src/queries/docs004.py"
|
||||||
import ormar
|
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
database = databases.Database("sqlite:///db.sqlite")
|
|
||||||
metadata = sqlalchemy.MetaData()
|
|
||||||
|
|
||||||
|
|
||||||
class ToDo(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "todos"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
|
||||||
text: ormar.String(max_length=500)
|
|
||||||
completed: ormar.Boolean(default=False)
|
|
||||||
|
|
||||||
# create multiple instances at once with bulk_create
|
|
||||||
await ToDo.objects.bulk_create(
|
|
||||||
[
|
|
||||||
ToDo(text="Buy the groceries."),
|
|
||||||
ToDo(text="Call Mum.", completed=True),
|
|
||||||
ToDo(text="Send invoices.", completed=True),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
todoes = await ToDo.objects.all()
|
|
||||||
assert len(todoes) == 3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### bulk_update
|
### bulk_update
|
||||||
@ -245,34 +170,8 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai
|
|||||||
|
|
||||||
Return number of rows deleted.
|
Return number of rows deleted.
|
||||||
|
|
||||||
```python hl_lines="23-27"
|
```python hl_lines="26-30"
|
||||||
import databases
|
--8<-- "../docs_src/queries/docs005.py"
|
||||||
import ormar
|
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
database = databases.Database("sqlite:///db.sqlite")
|
|
||||||
metadata = sqlalchemy.MetaData()
|
|
||||||
|
|
||||||
class Book(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "books"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
|
||||||
title: ormar.String(max_length=200)
|
|
||||||
author: ormar.String(max_length=100)
|
|
||||||
genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy'])
|
|
||||||
|
|
||||||
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure')
|
|
||||||
await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy')
|
|
||||||
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction')
|
|
||||||
|
|
||||||
# delete accepts kwargs that will be used in filter
|
|
||||||
# acting in same way as queryset.filter(**kwargs).delete()
|
|
||||||
await Book.objects.delete(genre='Fantasy') # delete all fantasy books
|
|
||||||
all_books = await Book.objects.all()
|
|
||||||
assert len(all_books) == 2
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### all
|
### all
|
||||||
@ -453,76 +352,8 @@ has_sample = await Book.objects.filter(title='Sample').exists()
|
|||||||
|
|
||||||
With `fields()` you can select subset of model columns to limit the data load.
|
With `fields()` you can select subset of model columns to limit the data load.
|
||||||
|
|
||||||
```python hl_lines="48 60 61 67"
|
```python hl_lines="47 59 60 66"
|
||||||
import databases
|
--8<-- "../docs_src/queries/docs006.py"
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
import ormar
|
|
||||||
from tests.settings import DATABASE_URL
|
|
||||||
|
|
||||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
|
||||||
metadata = sqlalchemy.MetaData()
|
|
||||||
|
|
||||||
|
|
||||||
class Company(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "companies"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
|
||||||
name: ormar.String(max_length=100)
|
|
||||||
founded: ormar.Integer(nullable=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Car(ormar.Model):
|
|
||||||
class Meta:
|
|
||||||
tablename = "cars"
|
|
||||||
metadata = metadata
|
|
||||||
database = database
|
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
|
||||||
manufacturer: ormar.ForeignKey(Company)
|
|
||||||
name: ormar.String(max_length=100)
|
|
||||||
year: ormar.Integer(nullable=True)
|
|
||||||
gearbox_type: ormar.String(max_length=20, nullable=True)
|
|
||||||
gears: ormar.Integer(nullable=True)
|
|
||||||
aircon_type: ormar.String(max_length=20, nullable=True)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# build some sample data
|
|
||||||
toyota = await Company.objects.create(name="Toyota", founded=1937)
|
|
||||||
await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5,
|
|
||||||
aircon_type='Manual')
|
|
||||||
await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5,
|
|
||||||
aircon_type='Manual')
|
|
||||||
await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6,
|
|
||||||
aircon_type='Auto')
|
|
||||||
|
|
||||||
# select manufacturer but only name - to include related models use notation {model_name}__{column}
|
|
||||||
all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__name']).all()
|
|
||||||
for car in all_cars:
|
|
||||||
# excluded columns will yield None
|
|
||||||
assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type'])
|
|
||||||
# included column on related models will be available, pk column is always included
|
|
||||||
# even if you do not include it in fields list
|
|
||||||
assert car.manufacturer.name == 'Toyota'
|
|
||||||
# also in the nested related models - you cannot exclude pk - it's always auto added
|
|
||||||
assert car.manufacturer.founded is None
|
|
||||||
|
|
||||||
# fields() can be called several times, building up the columns to select
|
|
||||||
# models selected in select_related but with no columns in fields list implies all fields
|
|
||||||
all_cars = await Car.objects.select_related('manufacturer').fields('id').fields(
|
|
||||||
['name']).all()
|
|
||||||
# all fiels from company model are selected
|
|
||||||
assert all_cars[0].manufacturer.name == 'Toyota'
|
|
||||||
assert all_cars[0].manufacturer.founded == 1937
|
|
||||||
|
|
||||||
# cannot exclude mandatory model columns - company__name in this example
|
|
||||||
await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__founded']).all()
|
|
||||||
# will raise pydantic ValidationError as company.name is required
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
!!!warning
|
!!!warning
|
||||||
@ -540,3 +371,4 @@ await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company_
|
|||||||
Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()`
|
Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()`
|
||||||
|
|
||||||
[models]: ./models.md
|
[models]: ./models.md
|
||||||
|
[relations]: ./relations.md
|
||||||
@ -15,7 +15,7 @@ Sqlalchemy column and Type are automatically taken from target `Model`.
|
|||||||
|
|
||||||
To define a relation add `ForeignKey` field that points to related `Model`.
|
To define a relation add `ForeignKey` field that points to related `Model`.
|
||||||
|
|
||||||
```Python hl_lines="27"
|
```Python hl_lines="29"
|
||||||
--8<-- "../docs_src/fields/docs003.py"
|
--8<-- "../docs_src/fields/docs003.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ To define a relation add `ForeignKey` field that points to related `Model`.
|
|||||||
|
|
||||||
By default it's child (source) `Model` name + s, like courses in snippet below:
|
By default it's child (source) `Model` name + s, like courses in snippet below:
|
||||||
|
|
||||||
```Python hl_lines="27 33"
|
```Python hl_lines="29 35"
|
||||||
--8<-- "../docs_src/fields/docs001.py"
|
--8<-- "../docs_src/fields/docs001.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ By default it's child (source) `Model` name + s, like courses in snippet below:
|
|||||||
|
|
||||||
But you can overwrite this name by providing `related_name` parameter like below:
|
But you can overwrite this name by providing `related_name` parameter like below:
|
||||||
|
|
||||||
```Python hl_lines="27 33"
|
```Python hl_lines="29 35"
|
||||||
--8<-- "../docs_src/fields/docs002.py"
|
--8<-- "../docs_src/fields/docs002.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ You have several ways to set-up a relationship connection.
|
|||||||
|
|
||||||
The most obvious one is to pass a related `Model` instance to the constructor.
|
The most obvious one is to pass a related `Model` instance to the constructor.
|
||||||
|
|
||||||
```Python hl_lines="32-33"
|
```Python hl_lines="34-35"
|
||||||
--8<-- "../docs_src/relations/docs001.py"
|
--8<-- "../docs_src/relations/docs001.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ The most obvious one is to pass a related `Model` instance to the constructor.
|
|||||||
|
|
||||||
You can setup the relation also with just the pk column value of the related model.
|
You can setup the relation also with just the pk column value of the related model.
|
||||||
|
|
||||||
```Python hl_lines="35-36"
|
```Python hl_lines="37-38"
|
||||||
--8<-- "../docs_src/relations/docs001.py"
|
--8<-- "../docs_src/relations/docs001.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ Next option is with a dictionary of key-values of the related model.
|
|||||||
|
|
||||||
You can build the dictionary yourself or get it from existing model with `dict()` method.
|
You can build the dictionary yourself or get it from existing model with `dict()` method.
|
||||||
|
|
||||||
```Python hl_lines="38-39"
|
```Python hl_lines="40-41"
|
||||||
--8<-- "../docs_src/relations/docs001.py"
|
--8<-- "../docs_src/relations/docs001.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ You can build the dictionary yourself or get it from existing model with `dict()
|
|||||||
|
|
||||||
Finally you can explicitly set it to None (default behavior if no value passed).
|
Finally you can explicitly set it to None (default behavior if no value passed).
|
||||||
|
|
||||||
```Python hl_lines="41-42"
|
```Python hl_lines="43-44"
|
||||||
--8<-- "../docs_src/relations/docs001.py"
|
--8<-- "../docs_src/relations/docs001.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -121,7 +121,11 @@ await news.posts.add(post)
|
|||||||
|
|
||||||
Otherwise an IntegrityError will be raised by your database driver library.
|
Otherwise an IntegrityError will be raised by your database driver library.
|
||||||
|
|
||||||
#### Creating new related `Model` instances
|
#### create()
|
||||||
|
|
||||||
|
Create related `Model` directly from parent `Model`.
|
||||||
|
|
||||||
|
The link table is automatically populated, as well as relation ids in the database.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Creating columns object from instance:
|
# Creating columns object from instance:
|
||||||
@ -136,15 +140,27 @@ assert len(await post.categories.all()) == 2
|
|||||||
|
|
||||||
To learn more about available QuerySet methods visit [queries][queries]
|
To learn more about available QuerySet methods visit [queries][queries]
|
||||||
|
|
||||||
#### Removing related models
|
#### remove()
|
||||||
|
|
||||||
|
Removal of the related model one by one.
|
||||||
|
|
||||||
|
Removes also the relation in the database.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Removal of the relationship by one
|
|
||||||
await news.posts.remove(post)
|
await news.posts.remove(post)
|
||||||
# or all at once
|
```
|
||||||
|
|
||||||
|
#### clear()
|
||||||
|
|
||||||
|
Removal all related models in one call.
|
||||||
|
|
||||||
|
Removes also the relation in the database.
|
||||||
|
|
||||||
|
```python
|
||||||
await news.posts.clear()
|
await news.posts.clear()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### All other queryset methods
|
#### Other queryset methods
|
||||||
|
|
||||||
When access directly the related `ManyToMany` field returns the list of related models.
|
When access directly the related `ManyToMany` field returns the list of related models.
|
||||||
|
|
||||||
@ -164,7 +180,18 @@ news_posts = await news.posts.select_related("author").all()
|
|||||||
assert news_posts[0].author == guido
|
assert news_posts[0].author == guido
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Currently supported methods are:
|
||||||
|
|
||||||
!!!tip
|
!!!tip
|
||||||
To learn more about available QuerySet methods visit [queries][queries]
|
To learn more about available QuerySet methods visit [queries][queries]
|
||||||
|
|
||||||
|
##### get()
|
||||||
|
##### all()
|
||||||
|
##### filter()
|
||||||
|
##### select_related()
|
||||||
|
##### limit()
|
||||||
|
##### offset()
|
||||||
|
##### count()
|
||||||
|
##### exists()
|
||||||
|
|
||||||
[queries]: ./queries.md
|
[queries]: ./queries.md
|
||||||
@ -1,6 +1,22 @@
|
|||||||
|
# 0.4.0
|
||||||
|
|
||||||
|
* Changed notation in Model definition -> now use name = ormar.Field() not name: ormar.Field()
|
||||||
|
* Note that old notation is still supported but deprecated and will not play nice with static checkers like mypy and pydantic pycharm plugin
|
||||||
|
* Type hint docs and test
|
||||||
|
* Use mypy for tests also not, only ormar package
|
||||||
|
* Fix scale and precision translation with max_digits and decimal_places pydantic Decimal field
|
||||||
|
* Update docs - add best practices for dependencies
|
||||||
|
* Refactor metaclass and model_fields to play nice with type hints
|
||||||
|
* Add mypy and pydantic plugin to docs
|
||||||
|
* Expand the docs on ManyToMany relation
|
||||||
|
|
||||||
|
# 0.3.11
|
||||||
|
|
||||||
|
* Fix setting server_default as default field value in python
|
||||||
|
|
||||||
# 0.3.10
|
# 0.3.10
|
||||||
|
|
||||||
* Fix
|
* Fix postgresql check to avoid exceptions with drivers not installed if using different backend
|
||||||
|
|
||||||
# 0.3.9
|
# 0.3.9
|
||||||
|
|
||||||
|
|||||||
0
docs_src/__init__.py
Normal file
0
docs_src/__init__.py
Normal file
0
docs_src/fastapi/__init__.py
Normal file
0
docs_src/fastapi/__init__.py
Normal file
@ -1,4 +1,4 @@
|
|||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -32,8 +32,8 @@ class Category(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Item(ormar.Model):
|
class Item(ormar.Model):
|
||||||
@ -42,9 +42,9 @@ class Item(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/items/", response_model=List[Item])
|
@app.get("/items/", response_model=List[Item])
|
||||||
|
|||||||
0
docs_src/fastapi/mypy/__init__.py
Normal file
0
docs_src/fastapi/mypy/__init__.py
Normal file
17
docs_src/fastapi/mypy/docs001.py
Normal file
17
docs_src/fastapi/mypy/docs001.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
database = database
|
||||||
|
metadata = metadata
|
||||||
|
|
||||||
|
id = ormar.Integer(primary_key=True)
|
||||||
|
name = ormar.String(max_length=100)
|
||||||
|
completed = ormar.Boolean(default=False)
|
||||||
0
docs_src/fields/__init__.py
Normal file
0
docs_src/fields/__init__.py
Normal file
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@ -12,8 +14,8 @@ class Department(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Course(ormar.Model):
|
class Course(ormar.Model):
|
||||||
@ -21,14 +23,14 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
department: ormar.ForeignKey(Department)
|
department: Optional[Department] = ormar.ForeignKey(Department)
|
||||||
|
|
||||||
|
|
||||||
department = Department(name='Science')
|
department = Department(name="Science")
|
||||||
course = Course(name='Math', completed=False, department=department)
|
course = Course(name="Math", completed=False, department=department)
|
||||||
|
|
||||||
print(department.courses[0])
|
print(department.courses[0])
|
||||||
# Will produce:
|
# Will produce:
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@ -12,8 +14,8 @@ class Department(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Course(ormar.Model):
|
class Course(ormar.Model):
|
||||||
@ -21,14 +23,14 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
department: ormar.ForeignKey(Department, related_name="my_courses")
|
department: Optional[Department] = ormar.ForeignKey(Department, related_name="my_courses")
|
||||||
|
|
||||||
|
|
||||||
department = Department(name='Science')
|
department = Department(name="Science")
|
||||||
course = Course(name='Math', completed=False, department=department)
|
course = Course(name="Math", completed=False, department=department)
|
||||||
|
|
||||||
print(department.my_courses[0])
|
print(department.my_courses[0])
|
||||||
# Will produce:
|
# Will produce:
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@ -12,8 +14,8 @@ class Department(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Course(ormar.Model):
|
class Course(ormar.Model):
|
||||||
@ -21,7 +23,7 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
department: ormar.ForeignKey(Department)
|
department: Optional[Department] = ormar.ForeignKey(Department)
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from sqlalchemy import func, text
|
from sqlalchemy import func, text
|
||||||
@ -14,8 +16,8 @@ class Product(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
company: ormar.String(max_length=200, server_default='Acme')
|
company: str = ormar.String(max_length=200, server_default="Acme")
|
||||||
sort_order: ormar.Integer(server_default=text("10"))
|
sort_order: int = ormar.Integer(server_default=text("10"))
|
||||||
created: ormar.DateTime(server_default=func.now())
|
created: datetime = ormar.DateTime(server_default=func.now())
|
||||||
|
|||||||
0
docs_src/models/__init__.py
Normal file
0
docs_src/models/__init__.py
Normal file
@ -12,6 +12,6 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|||||||
@ -15,6 +15,6 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|||||||
@ -12,9 +12,9 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|
||||||
|
|
||||||
print(Course.__fields__)
|
print(Course.__fields__)
|
||||||
|
|||||||
@ -8,13 +8,13 @@ metadata = sqlalchemy.MetaData()
|
|||||||
|
|
||||||
|
|
||||||
class Course(ormar.Model):
|
class Course(ormar.Model):
|
||||||
class Meta:
|
class Meta(ormar.ModelMeta): # note you don't have to subclass - but it's recommended for ide completion and mypy
|
||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|
||||||
|
|
||||||
print(Course.Meta.table.columns)
|
print(Course.Meta.table.columns)
|
||||||
|
|||||||
@ -8,15 +8,16 @@ metadata = sqlalchemy.MetaData()
|
|||||||
|
|
||||||
|
|
||||||
class Course(ormar.Model):
|
class Course(ormar.Model):
|
||||||
class Meta:
|
class Meta(ormar.ModelMeta):
|
||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|
||||||
print({x:v.__dict__ for x,v in Course.Meta.model_fields.items()})
|
|
||||||
|
print({x: v.__dict__ for x, v in Course.Meta.model_fields.items()})
|
||||||
"""
|
"""
|
||||||
Will produce:
|
Will produce:
|
||||||
{'completed': mappingproxy({'autoincrement': False,
|
{'completed': mappingproxy({'autoincrement': False,
|
||||||
|
|||||||
@ -14,8 +14,8 @@ class Course(ormar.Model):
|
|||||||
# define your constraints in Meta class of the model
|
# define your constraints in Meta class of the model
|
||||||
# it's a list that can contain multiple constraints
|
# it's a list that can contain multiple constraints
|
||||||
# hera a combination of name and column will have to be unique in db
|
# hera a combination of name and column will have to be unique in db
|
||||||
constraints = [ormar.UniqueColumns('name', 'completed')]
|
constraints = [ormar.UniqueColumns("name", "completed")]
|
||||||
|
|
||||||
id = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name = ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed = ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|||||||
@ -12,9 +12,9 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id = ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name = ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed = ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|
||||||
|
|
||||||
course = Course(name="Painting for dummies", completed=False)
|
course = Course(name="Painting for dummies", completed=False)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class Child(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(name='child_id', primary_key=True)
|
id: int = ormar.Integer(name="child_id", primary_key=True)
|
||||||
first_name: ormar.String(name='fname', max_length=100)
|
first_name: str = ormar.String(name="fname", max_length=100)
|
||||||
last_name: ormar.String(name='lname', max_length=100)
|
last_name: str = ormar.String(name="lname", max_length=100)
|
||||||
born_year: ormar.Integer(name='year_born', nullable=True)
|
born_year: int = ormar.Integer(name="year_born", nullable=True)
|
||||||
|
|||||||
@ -1,9 +1,21 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from .docs010 import Artist # previous example
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///test.db", force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
class Album(ormar.Model):
|
class Album(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "music_albums"
|
tablename = "music_albums"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(name='album_id', primary_key=True)
|
id: int = ormar.Integer(name="album_id", primary_key=True)
|
||||||
name: ormar.String(name='album_name', max_length=100)
|
name: str = ormar.String(name="album_name", max_length=100)
|
||||||
artist: ormar.ForeignKey(Artist, name='artist_id')
|
artist: Optional[Artist] = ormar.ForeignKey(Artist, name="artist_id")
|
||||||
|
|||||||
@ -1,3 +1,13 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from .docs008 import Child
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///test.db", force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
class ArtistChildren(ormar.Model):
|
class ArtistChildren(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "children_x_artists"
|
tablename = "children_x_artists"
|
||||||
@ -11,8 +21,8 @@ class Artist(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(name='artist_id', primary_key=True)
|
id: int = ormar.Integer(name="artist_id", primary_key=True)
|
||||||
first_name: ormar.String(name='fname', max_length=100)
|
first_name: str = ormar.String(name="fname", max_length=100)
|
||||||
last_name: ormar.String(name='lname', max_length=100)
|
last_name: str = ormar.String(name="lname", max_length=100)
|
||||||
born_year: ormar.Integer(name='year')
|
born_year: int = ormar.Integer(name="year")
|
||||||
children: ormar.ManyToMany(Child, through=ArtistChildren)
|
children = ormar.ManyToMany(Child, through=ArtistChildren)
|
||||||
|
|||||||
19
docs_src/models/docs011.py
Normal file
19
docs_src/models/docs011.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
database = database
|
||||||
|
metadata = metadata
|
||||||
|
|
||||||
|
id: ormar.Integer(primary_key=True)
|
||||||
|
name: ormar.String(max_length=100)
|
||||||
|
completed: ormar.Boolean(default=False)
|
||||||
|
|
||||||
|
c1 = Course()
|
||||||
17
docs_src/models/docs012.py
Normal file
17
docs_src/models/docs012.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
database = database
|
||||||
|
metadata = metadata
|
||||||
|
|
||||||
|
id = ormar.Integer(primary_key=True)
|
||||||
|
name = ormar.String(max_length=100)
|
||||||
|
completed = ormar.Boolean(default=False)
|
||||||
38
docs_src/models/docs013.py
Normal file
38
docs_src/models/docs013.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///test.db", force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
# note that you do not have to subclass ModelMeta,
|
||||||
|
# it's useful for type hints and code completion
|
||||||
|
class MainMeta(ormar.ModelMeta):
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
|
||||||
|
class Artist(ormar.Model):
|
||||||
|
class Meta(MainMeta):
|
||||||
|
# note that tablename is optional
|
||||||
|
# if not provided ormar will user class.__name__.lower()+'s'
|
||||||
|
# -> artists in this example
|
||||||
|
pass
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
first_name: str = ormar.String(max_length=100)
|
||||||
|
last_name: str = ormar.String(max_length=100)
|
||||||
|
born_year: int = ormar.Integer(name="year")
|
||||||
|
|
||||||
|
|
||||||
|
class Album(ormar.Model):
|
||||||
|
class Meta(MainMeta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
artist: Optional[Artist] = ormar.ForeignKey(Artist)
|
||||||
0
docs_src/queries/__init__.py
Normal file
0
docs_src/queries/__init__.py
Normal file
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import ormar
|
import ormar
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -12,8 +14,8 @@ class Album(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Track(ormar.Model):
|
class Track(ormar.Model):
|
||||||
@ -22,7 +24,7 @@ class Track(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
album: ormar.ForeignKey(Album)
|
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||||
title: ormar.String(max_length=100)
|
title: str = ormar.String(max_length=100)
|
||||||
position: ormar.Integer()
|
position: int = ormar.Integer()
|
||||||
|
|||||||
28
docs_src/queries/docs002.py
Normal file
28
docs_src/queries/docs002.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import databases
|
||||||
|
import ormar
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Book(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "books"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
title: str = ormar.String(max_length=200)
|
||||||
|
author: str = ormar.String(max_length=100)
|
||||||
|
genre: str = ormar.String(max_length=100, default='Fiction',
|
||||||
|
choices=['Fiction', 'Adventure', 'Historic', 'Fantasy'])
|
||||||
|
|
||||||
|
|
||||||
|
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure')
|
||||||
|
await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction')
|
||||||
|
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction')
|
||||||
|
|
||||||
|
await Book.objects.update(each=True, genre='Fiction')
|
||||||
|
all_books = await Book.objects.filter(genre='Fiction').all()
|
||||||
|
assert len(all_books) == 3
|
||||||
32
docs_src/queries/docs003.py
Normal file
32
docs_src/queries/docs003.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import databases
|
||||||
|
import ormar
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Book(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "books"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
title: str = ormar.String(max_length=200)
|
||||||
|
author: str = ormar.String(max_length=100)
|
||||||
|
genre: str = ormar.String(max_length=100, default='Fiction',
|
||||||
|
choices=['Fiction', 'Adventure', 'Historic', 'Fantasy'])
|
||||||
|
|
||||||
|
|
||||||
|
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure')
|
||||||
|
await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction')
|
||||||
|
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction')
|
||||||
|
|
||||||
|
# if not exist the instance will be persisted in db
|
||||||
|
vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction')
|
||||||
|
assert await Book.objects.count() == 1
|
||||||
|
|
||||||
|
# if pk or pkname passed in kwargs (like id here) the object will be updated
|
||||||
|
assert await Book.objects.update_or_create(id=vol2.id, genre='Historic')
|
||||||
|
assert await Book.objects.count() == 1
|
||||||
30
docs_src/queries/docs004.py
Normal file
30
docs_src/queries/docs004.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import databases
|
||||||
|
import ormar
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class ToDo(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "todos"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
text: str = ormar.String(max_length=500)
|
||||||
|
completed = ormar.Boolean(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
# create multiple instances at once with bulk_create
|
||||||
|
await ToDo.objects.bulk_create(
|
||||||
|
[
|
||||||
|
ToDo(text="Buy the groceries."),
|
||||||
|
ToDo(text="Call Mum.", completed=True),
|
||||||
|
ToDo(text="Send invoices.", completed=True),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
todoes = await ToDo.objects.all()
|
||||||
|
assert len(todoes) == 3
|
||||||
30
docs_src/queries/docs005.py
Normal file
30
docs_src/queries/docs005.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import databases
|
||||||
|
import ormar
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Book(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "books"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
title: str = ormar.String(max_length=200)
|
||||||
|
author: str = ormar.String(max_length=100)
|
||||||
|
genre: str = ormar.String(max_length=100, default='Fiction',
|
||||||
|
choices=['Fiction', 'Adventure', 'Historic', 'Fantasy'])
|
||||||
|
|
||||||
|
|
||||||
|
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure')
|
||||||
|
await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy')
|
||||||
|
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction')
|
||||||
|
|
||||||
|
# delete accepts kwargs that will be used in filter
|
||||||
|
# acting in same way as queryset.filter(**kwargs).delete()
|
||||||
|
await Book.objects.delete(genre='Fantasy') # delete all fantasy books
|
||||||
|
all_books = await Book.objects.all()
|
||||||
|
assert len(all_books) == 2
|
||||||
67
docs_src/queries/docs006.py
Normal file
67
docs_src/queries/docs006.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
founded: int = ormar.Integer(nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Car(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "cars"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
manufacturer = ormar.ForeignKey(Company)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
year: int = ormar.Integer(nullable=True)
|
||||||
|
gearbox_type: str = ormar.String(max_length=20, nullable=True)
|
||||||
|
gears: int = ormar.Integer(nullable=True)
|
||||||
|
aircon_type: str = ormar.String(max_length=20, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
# build some sample data
|
||||||
|
toyota = await Company.objects.create(name="Toyota", founded=1937)
|
||||||
|
await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5,
|
||||||
|
aircon_type='Manual')
|
||||||
|
await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5,
|
||||||
|
aircon_type='Manual')
|
||||||
|
await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6,
|
||||||
|
aircon_type='Auto')
|
||||||
|
|
||||||
|
# select manufacturer but only name - to include related models use notation {model_name}__{column}
|
||||||
|
all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__name']).all()
|
||||||
|
for car in all_cars:
|
||||||
|
# excluded columns will yield None
|
||||||
|
assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type'])
|
||||||
|
# included column on related models will be available, pk column is always included
|
||||||
|
# even if you do not include it in fields list
|
||||||
|
assert car.manufacturer.name == 'Toyota'
|
||||||
|
# also in the nested related models - you cannot exclude pk - it's always auto added
|
||||||
|
assert car.manufacturer.founded is None
|
||||||
|
|
||||||
|
# fields() can be called several times, building up the columns to select
|
||||||
|
# models selected in select_related but with no columns in fields list implies all fields
|
||||||
|
all_cars = await Car.objects.select_related('manufacturer').fields('id').fields(
|
||||||
|
['name']).all()
|
||||||
|
# all fiels from company model are selected
|
||||||
|
assert all_cars[0].manufacturer.name == 'Toyota'
|
||||||
|
assert all_cars[0].manufacturer.founded == 1937
|
||||||
|
|
||||||
|
# cannot exclude mandatory model columns - company__name in this example
|
||||||
|
await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__founded']).all()
|
||||||
|
# will raise pydantic ValidationError as company.name is required
|
||||||
0
docs_src/relations/__init__.py
Normal file
0
docs_src/relations/__init__.py
Normal file
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional, Dict, Union
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@ -12,8 +14,8 @@ class Department(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Course(ormar.Model):
|
class Course(ormar.Model):
|
||||||
@ -21,22 +23,22 @@ class Course(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
department: ormar.ForeignKey(Department)
|
department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)
|
||||||
|
|
||||||
|
|
||||||
department = Department(name='Science')
|
department = Department(name="Science")
|
||||||
|
|
||||||
# set up a relation with actual Model instance
|
# set up a relation with actual Model instance
|
||||||
course = Course(name='Math', completed=False, department=department)
|
course = Course(name="Math", completed=False, department=department)
|
||||||
|
|
||||||
# set up relation with only related model pk value
|
# set up relation with only related model pk value
|
||||||
course2 = Course(name='Math II', completed=False, department=department.pk)
|
course2 = Course(name="Math II", completed=False, department=department.pk)
|
||||||
|
|
||||||
# set up a relation with dictionary corresponding to related model
|
# set up a relation with dictionary corresponding to related model
|
||||||
course3 = Course(name='Math III', completed=False, department=department.dict())
|
course3 = Course(name="Math III", completed=False, department=department.dict())
|
||||||
|
|
||||||
# explicitly set up None
|
# explicitly set up None
|
||||||
course4 = Course(name='Math III', completed=False, department=None)
|
course4 = Course(name="Math III", completed=False, department=None)
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional, Union, List
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import ormar
|
import ormar
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -12,9 +14,9 @@ class Author(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
first_name: ormar.String(max_length=80)
|
first_name: str = ormar.String(max_length=80)
|
||||||
last_name: ormar.String(max_length=80)
|
last_name: str = ormar.String(max_length=80)
|
||||||
|
|
||||||
|
|
||||||
class Category(ormar.Model):
|
class Category(ormar.Model):
|
||||||
@ -23,8 +25,8 @@ class Category(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=40)
|
name: str = ormar.String(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
class PostCategory(ormar.Model):
|
class PostCategory(ormar.Model):
|
||||||
@ -42,7 +44,9 @@ class Post(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
title: ormar.String(max_length=200)
|
title: str = ormar.String(max_length=200)
|
||||||
categories: ormar.ManyToMany(Category, through=PostCategory)
|
categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
||||||
author: ormar.ForeignKey(Author)
|
Category, through=PostCategory
|
||||||
|
)
|
||||||
|
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||||
|
|||||||
@ -8,6 +8,8 @@ nav:
|
|||||||
- Relations: relations.md
|
- Relations: relations.md
|
||||||
- Queries: queries.md
|
- Queries: queries.md
|
||||||
- Use with Fastapi: fastapi.md
|
- Use with Fastapi: fastapi.md
|
||||||
|
- Use with mypy: mypy.md
|
||||||
|
- PyCharm plugin: plugin.md
|
||||||
- Contributing: contributing.md
|
- Contributing: contributing.md
|
||||||
- Release Notes: releases.md
|
- Release Notes: releases.md
|
||||||
repo_name: collerek/ormar
|
repo_name: collerek/ormar
|
||||||
|
|||||||
5
mypy.ini
5
mypy.ini
@ -1,5 +1,10 @@
|
|||||||
[mypy]
|
[mypy]
|
||||||
python_version = 3.8
|
python_version = 3.8
|
||||||
|
plugins = pydantic.mypy
|
||||||
|
|
||||||
[mypy-sqlalchemy.*]
|
[mypy-sqlalchemy.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-tests.test_model_definition.*]
|
||||||
|
ignore_errors = True
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from ormar.exceptions import ModelDefinitionError, ModelNotSet, MultipleMatches, NoMatch
|
from ormar.exceptions import ModelDefinitionError, ModelNotSet, MultipleMatches, NoMatch
|
||||||
from ormar.fields import (
|
from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I100
|
||||||
|
from ormar.fields import ( # noqa: I100
|
||||||
BigInteger,
|
BigInteger,
|
||||||
Boolean,
|
Boolean,
|
||||||
Date,
|
Date,
|
||||||
@ -17,6 +18,7 @@ from ormar.fields import (
|
|||||||
UniqueColumns,
|
UniqueColumns,
|
||||||
)
|
)
|
||||||
from ormar.models import Model
|
from ormar.models import Model
|
||||||
|
from ormar.models.metaclass import ModelMeta
|
||||||
from ormar.queryset import QuerySet
|
from ormar.queryset import QuerySet
|
||||||
from ormar.relations import RelationType
|
from ormar.relations import RelationType
|
||||||
|
|
||||||
@ -28,8 +30,7 @@ class UndefinedType: # pragma no cover
|
|||||||
|
|
||||||
Undefined = UndefinedType()
|
Undefined = UndefinedType()
|
||||||
|
|
||||||
|
__version__ = "0.4.0"
|
||||||
__version__ = "0.3.11"
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Integer",
|
"Integer",
|
||||||
"BigInteger",
|
"BigInteger",
|
||||||
@ -54,4 +55,7 @@ __all__ = [
|
|||||||
"Undefined",
|
"Undefined",
|
||||||
"UUID",
|
"UUID",
|
||||||
"UniqueColumns",
|
"UniqueColumns",
|
||||||
|
"QuerySetProtocol",
|
||||||
|
"RelationProtocol",
|
||||||
|
"ModelMeta",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
|
import pydantic
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from pydantic import Field, typing
|
from pydantic import Field, typing
|
||||||
from pydantic.fields import FieldInfo
|
from pydantic.fields import FieldInfo
|
||||||
@ -11,7 +12,7 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
from ormar.models import NewBaseModel
|
from ormar.models import NewBaseModel
|
||||||
|
|
||||||
|
|
||||||
class BaseField:
|
class BaseField(FieldInfo):
|
||||||
__type__ = None
|
__type__ = None
|
||||||
|
|
||||||
column_type: sqlalchemy.Column
|
column_type: sqlalchemy.Column
|
||||||
@ -32,6 +33,28 @@ class BaseField:
|
|||||||
default: Any
|
default: Any
|
||||||
server_default: Any
|
server_default: Any
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_valid_field_info_field(cls, field_name: str) -> bool:
|
||||||
|
return (
|
||||||
|
field_name not in ["default", "default_factory"]
|
||||||
|
and not field_name.startswith("__")
|
||||||
|
and hasattr(cls, field_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_to_pydantic_field_info(cls, allow_null: bool = False) -> FieldInfo:
|
||||||
|
base = cls.default_value()
|
||||||
|
if base is None:
|
||||||
|
base = (
|
||||||
|
FieldInfo(default=None)
|
||||||
|
if (cls.nullable or allow_null)
|
||||||
|
else FieldInfo(default=pydantic.fields.Undefined)
|
||||||
|
)
|
||||||
|
for attr_name in FieldInfo.__dict__.keys():
|
||||||
|
if cls.is_valid_field_info_field(attr_name):
|
||||||
|
setattr(base, attr_name, cls.__dict__.get(attr_name))
|
||||||
|
return base
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_value(cls, use_server: bool = False) -> Optional[FieldInfo]:
|
def default_value(cls, use_server: bool = False) -> Optional[FieldInfo]:
|
||||||
if cls.is_auto_primary_key():
|
if cls.is_auto_primary_key():
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Generator, List, Optional, TYPE_CHECKING, Type, Union
|
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from sqlalchemy import UniqueConstraint
|
from sqlalchemy import UniqueConstraint
|
||||||
@ -37,10 +37,16 @@ def ForeignKey( # noqa CFQ002
|
|||||||
virtual: bool = False,
|
virtual: bool = False,
|
||||||
onupdate: str = None,
|
onupdate: str = None,
|
||||||
ondelete: str = None,
|
ondelete: str = None,
|
||||||
) -> Type["ForeignKeyField"]:
|
) -> Any:
|
||||||
fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname)
|
fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname)
|
||||||
to_field = to.__fields__[to.Meta.pkname]
|
to_field = to.Meta.model_fields[to.Meta.pkname]
|
||||||
|
__type__ = (
|
||||||
|
Union[to_field.__type__, to]
|
||||||
|
if not nullable
|
||||||
|
else Optional[Union[to_field.__type__, to]]
|
||||||
|
)
|
||||||
namespace = dict(
|
namespace = dict(
|
||||||
|
__type__=__type__,
|
||||||
to=to,
|
to=to,
|
||||||
name=name,
|
name=name,
|
||||||
nullable=nullable,
|
nullable=nullable,
|
||||||
@ -50,7 +56,7 @@ def ForeignKey( # noqa CFQ002
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
unique=unique,
|
unique=unique,
|
||||||
column_type=to_field.type_.column_type,
|
column_type=to_field.column_type,
|
||||||
related_name=related_name,
|
related_name=related_name,
|
||||||
virtual=virtual,
|
virtual=virtual,
|
||||||
primary_key=False,
|
primary_key=False,
|
||||||
@ -58,7 +64,6 @@ def ForeignKey( # noqa CFQ002
|
|||||||
pydantic_only=False,
|
pydantic_only=False,
|
||||||
default=None,
|
default=None,
|
||||||
server_default=None,
|
server_default=None,
|
||||||
__pydantic_model__=to,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return type("ForeignKey", (ForeignKeyField, BaseField), namespace)
|
return type("ForeignKey", (ForeignKeyField, BaseField), namespace)
|
||||||
@ -70,14 +75,6 @@ class ForeignKeyField(BaseField):
|
|||||||
related_name: str
|
related_name: str
|
||||||
virtual: bool
|
virtual: bool
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls) -> Generator:
|
|
||||||
yield cls.validate
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, value: Any) -> Any:
|
|
||||||
return value
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _extract_model_from_sequence(
|
def _extract_model_from_sequence(
|
||||||
cls, value: List, child: "Model", to_register: bool
|
cls, value: List, child: "Model", to_register: bool
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from typing import Dict, TYPE_CHECKING, Type
|
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
|
import ormar
|
||||||
from ormar.fields import BaseField
|
from ormar.fields import BaseField
|
||||||
from ormar.fields.foreign_key import ForeignKeyField
|
from ormar.fields.foreign_key import ForeignKeyField
|
||||||
|
|
||||||
@ -15,17 +16,25 @@ def ManyToMany(
|
|||||||
*,
|
*,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
unique: bool = False,
|
unique: bool = False,
|
||||||
related_name: str = None,
|
|
||||||
virtual: bool = False,
|
virtual: bool = False,
|
||||||
) -> Type["ManyToManyField"]:
|
**kwargs: Any
|
||||||
to_field = to.__fields__[to.Meta.pkname]
|
) -> Any:
|
||||||
|
to_field = to.Meta.model_fields[to.Meta.pkname]
|
||||||
|
related_name = kwargs.pop("related_name", None)
|
||||||
|
nullable = kwargs.pop("nullable", True)
|
||||||
|
__type__ = (
|
||||||
|
Union[to_field.__type__, to, List[to]] # type: ignore
|
||||||
|
if not nullable
|
||||||
|
else Optional[Union[to_field.__type__, to, List[to]]] # type: ignore
|
||||||
|
)
|
||||||
namespace = dict(
|
namespace = dict(
|
||||||
|
__type__=__type__,
|
||||||
to=to,
|
to=to,
|
||||||
through=through,
|
through=through,
|
||||||
name=name,
|
name=name,
|
||||||
nullable=True,
|
nullable=True,
|
||||||
unique=unique,
|
unique=unique,
|
||||||
column_type=to_field.type_.column_type,
|
column_type=to_field.column_type,
|
||||||
related_name=related_name,
|
related_name=related_name,
|
||||||
virtual=virtual,
|
virtual=virtual,
|
||||||
primary_key=False,
|
primary_key=False,
|
||||||
@ -33,20 +42,10 @@ def ManyToMany(
|
|||||||
pydantic_only=False,
|
pydantic_only=False,
|
||||||
default=None,
|
default=None,
|
||||||
server_default=None,
|
server_default=None,
|
||||||
__pydantic_model__=to,
|
|
||||||
# __origin__=List,
|
|
||||||
# __args__=[Optional[to]]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return type("ManyToMany", (ManyToManyField, BaseField), namespace)
|
return type("ManyToMany", (ManyToManyField, BaseField), namespace)
|
||||||
|
|
||||||
|
|
||||||
class ManyToManyField(ForeignKeyField):
|
class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationProtocol):
|
||||||
through: Type["Model"]
|
through: Type["Model"]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema: Dict) -> None:
|
|
||||||
field_schema["type"] = "array"
|
|
||||||
field_schema["title"] = cls.name.title()
|
|
||||||
field_schema["definitions"] = {f"{cls.to.__name__}": cls.to.schema()}
|
|
||||||
field_schema["items"] = {"$ref": f"{REF_PREFIX}{cls.to.__name__}"}
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Optional, Type
|
from typing import Any, Optional, TYPE_CHECKING, Type
|
||||||
|
|
||||||
import pydantic
|
import pydantic
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -20,7 +20,7 @@ def is_field_nullable(
|
|||||||
|
|
||||||
|
|
||||||
class ModelFieldFactory:
|
class ModelFieldFactory:
|
||||||
_bases: Any = BaseField
|
_bases: Any = (BaseField,)
|
||||||
_type: Any = None
|
_type: Any = None
|
||||||
|
|
||||||
def __new__(cls, *args: Any, **kwargs: Any) -> Type[BaseField]: # type: ignore
|
def __new__(cls, *args: Any, **kwargs: Any) -> Type[BaseField]: # type: ignore
|
||||||
@ -56,8 +56,7 @@ class ModelFieldFactory:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class String(ModelFieldFactory):
|
class String(ModelFieldFactory, str):
|
||||||
_bases = (pydantic.ConstrainedStr, BaseField)
|
|
||||||
_type = str
|
_type = str
|
||||||
|
|
||||||
def __new__( # type: ignore # noqa CFQ002
|
def __new__( # type: ignore # noqa CFQ002
|
||||||
@ -95,8 +94,7 @@ class String(ModelFieldFactory):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Integer(ModelFieldFactory):
|
class Integer(ModelFieldFactory, int):
|
||||||
_bases = (pydantic.ConstrainedInt, BaseField)
|
|
||||||
_type = int
|
_type = int
|
||||||
|
|
||||||
def __new__( # type: ignore
|
def __new__( # type: ignore
|
||||||
@ -130,8 +128,7 @@ class Integer(ModelFieldFactory):
|
|||||||
return sqlalchemy.Integer()
|
return sqlalchemy.Integer()
|
||||||
|
|
||||||
|
|
||||||
class Text(ModelFieldFactory):
|
class Text(ModelFieldFactory, str):
|
||||||
_bases = (pydantic.ConstrainedStr, BaseField)
|
|
||||||
_type = str
|
_type = str
|
||||||
|
|
||||||
def __new__( # type: ignore
|
def __new__( # type: ignore
|
||||||
@ -153,8 +150,7 @@ class Text(ModelFieldFactory):
|
|||||||
return sqlalchemy.Text()
|
return sqlalchemy.Text()
|
||||||
|
|
||||||
|
|
||||||
class Float(ModelFieldFactory):
|
class Float(ModelFieldFactory, float):
|
||||||
_bases = (pydantic.ConstrainedFloat, BaseField)
|
|
||||||
_type = float
|
_type = float
|
||||||
|
|
||||||
def __new__( # type: ignore
|
def __new__( # type: ignore
|
||||||
@ -182,17 +178,23 @@ class Float(ModelFieldFactory):
|
|||||||
return sqlalchemy.Float()
|
return sqlalchemy.Float()
|
||||||
|
|
||||||
|
|
||||||
class Boolean(ModelFieldFactory):
|
if TYPE_CHECKING: # pragma: nocover
|
||||||
_bases = (int, BaseField)
|
|
||||||
_type = bool
|
|
||||||
|
|
||||||
@classmethod
|
def Boolean(**kwargs: Any) -> bool:
|
||||||
def get_column_type(cls, **kwargs: Any) -> Any:
|
pass
|
||||||
return sqlalchemy.Boolean()
|
|
||||||
|
|
||||||
|
|
||||||
class DateTime(ModelFieldFactory):
|
else:
|
||||||
_bases = (datetime.datetime, BaseField)
|
|
||||||
|
class Boolean(ModelFieldFactory, int):
|
||||||
|
_type = bool
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_column_type(cls, **kwargs: Any) -> Any:
|
||||||
|
return sqlalchemy.Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class DateTime(ModelFieldFactory, datetime.datetime):
|
||||||
_type = datetime.datetime
|
_type = datetime.datetime
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -200,8 +202,7 @@ class DateTime(ModelFieldFactory):
|
|||||||
return sqlalchemy.DateTime()
|
return sqlalchemy.DateTime()
|
||||||
|
|
||||||
|
|
||||||
class Date(ModelFieldFactory):
|
class Date(ModelFieldFactory, datetime.date):
|
||||||
_bases = (datetime.date, BaseField)
|
|
||||||
_type = datetime.date
|
_type = datetime.date
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -209,8 +210,7 @@ class Date(ModelFieldFactory):
|
|||||||
return sqlalchemy.Date()
|
return sqlalchemy.Date()
|
||||||
|
|
||||||
|
|
||||||
class Time(ModelFieldFactory):
|
class Time(ModelFieldFactory, datetime.time):
|
||||||
_bases = (datetime.time, BaseField)
|
|
||||||
_type = datetime.time
|
_type = datetime.time
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -218,8 +218,7 @@ class Time(ModelFieldFactory):
|
|||||||
return sqlalchemy.Time()
|
return sqlalchemy.Time()
|
||||||
|
|
||||||
|
|
||||||
class JSON(ModelFieldFactory):
|
class JSON(ModelFieldFactory, pydantic.Json):
|
||||||
_bases = (pydantic.Json, BaseField)
|
|
||||||
_type = pydantic.Json
|
_type = pydantic.Json
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -227,8 +226,7 @@ class JSON(ModelFieldFactory):
|
|||||||
return sqlalchemy.JSON()
|
return sqlalchemy.JSON()
|
||||||
|
|
||||||
|
|
||||||
class BigInteger(Integer):
|
class BigInteger(Integer, int):
|
||||||
_bases = (pydantic.ConstrainedInt, BaseField)
|
|
||||||
_type = int
|
_type = int
|
||||||
|
|
||||||
def __new__( # type: ignore
|
def __new__( # type: ignore
|
||||||
@ -262,8 +260,7 @@ class BigInteger(Integer):
|
|||||||
return sqlalchemy.BigInteger()
|
return sqlalchemy.BigInteger()
|
||||||
|
|
||||||
|
|
||||||
class Decimal(ModelFieldFactory):
|
class Decimal(ModelFieldFactory, decimal.Decimal):
|
||||||
_bases = (pydantic.ConstrainedDecimal, BaseField)
|
|
||||||
_type = decimal.Decimal
|
_type = decimal.Decimal
|
||||||
|
|
||||||
def __new__( # type: ignore # noqa CFQ002
|
def __new__( # type: ignore # noqa CFQ002
|
||||||
@ -290,14 +287,14 @@ class Decimal(ModelFieldFactory):
|
|||||||
kwargs["le"] = kwargs["maximum"]
|
kwargs["le"] = kwargs["maximum"]
|
||||||
|
|
||||||
if kwargs.get("max_digits"):
|
if kwargs.get("max_digits"):
|
||||||
kwargs["scale"] = kwargs["max_digits"]
|
kwargs["precision"] = kwargs["max_digits"]
|
||||||
elif kwargs.get("scale"):
|
elif kwargs.get("precision"):
|
||||||
kwargs["max_digits"] = kwargs["scale"]
|
kwargs["max_digits"] = kwargs["precision"]
|
||||||
|
|
||||||
if kwargs.get("decimal_places"):
|
if kwargs.get("decimal_places"):
|
||||||
kwargs["precision"] = kwargs["decimal_places"]
|
kwargs["scale"] = kwargs["decimal_places"]
|
||||||
elif kwargs.get("precision"):
|
elif kwargs.get("scale"):
|
||||||
kwargs["decimal_places"] = kwargs["precision"]
|
kwargs["decimal_places"] = kwargs["scale"]
|
||||||
|
|
||||||
return super().__new__(cls, **kwargs)
|
return super().__new__(cls, **kwargs)
|
||||||
|
|
||||||
@ -317,8 +314,7 @@ class Decimal(ModelFieldFactory):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UUID(ModelFieldFactory):
|
class UUID(ModelFieldFactory, uuid.UUID):
|
||||||
_bases = (uuid.UUID, BaseField)
|
|
||||||
_type = uuid.UUID
|
_type = uuid.UUID
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pydantic
|
import pydantic
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from pydantic import BaseConfig
|
from pydantic import BaseConfig
|
||||||
from pydantic.fields import FieldInfo, ModelField
|
from pydantic.fields import ModelField
|
||||||
|
from pydantic.utils import lenient_issubclass
|
||||||
from sqlalchemy.sql.schema import ColumnCollectionConstraint
|
from sqlalchemy.sql.schema import ColumnCollectionConstraint
|
||||||
|
|
||||||
import ormar # noqa I100
|
import ormar # noqa I100
|
||||||
@ -179,44 +181,49 @@ def register_relation_in_alias_manager(
|
|||||||
|
|
||||||
|
|
||||||
def populate_default_pydantic_field_value(
|
def populate_default_pydantic_field_value(
|
||||||
type_: Type[BaseField], field: str, attrs: dict
|
ormar_field: Type[BaseField], field_name: str, attrs: dict
|
||||||
) -> dict:
|
) -> dict:
|
||||||
def_value = type_.default_value()
|
curr_def_value = attrs.get(field_name, ormar.Undefined)
|
||||||
curr_def_value = attrs.get(field, "NONE")
|
if lenient_issubclass(curr_def_value, ormar.fields.BaseField):
|
||||||
if curr_def_value == "NONE" and isinstance(def_value, FieldInfo):
|
curr_def_value = ormar.Undefined
|
||||||
attrs[field] = def_value
|
if curr_def_value is None:
|
||||||
elif curr_def_value == "NONE" and type_.nullable:
|
attrs[field_name] = ormar_field.convert_to_pydantic_field_info(allow_null=True)
|
||||||
attrs[field] = FieldInfo(default=None)
|
else:
|
||||||
|
attrs[field_name] = ormar_field.convert_to_pydantic_field_info()
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
def populate_pydantic_default_values(attrs: Dict) -> Dict:
|
def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]:
|
||||||
for field, type_ in attrs["__annotations__"].items():
|
model_fields = {}
|
||||||
if issubclass(type_, BaseField):
|
potential_fields = {
|
||||||
if type_.name is None:
|
k: v
|
||||||
type_.name = field
|
for k, v in attrs["__annotations__"].items()
|
||||||
attrs = populate_default_pydantic_field_value(type_, field, attrs)
|
if lenient_issubclass(v, BaseField)
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
def extract_annotations_and_default_vals(attrs: dict, bases: Tuple) -> dict:
|
|
||||||
attrs["__annotations__"] = attrs.get("__annotations__") or bases[0].__dict__.get(
|
|
||||||
"__annotations__", {}
|
|
||||||
)
|
|
||||||
attrs = populate_pydantic_default_values(attrs)
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
def populate_meta_orm_model_fields(
|
|
||||||
attrs: dict, new_model: Type["Model"]
|
|
||||||
) -> Type["Model"]:
|
|
||||||
model_fields = {
|
|
||||||
field_name: field
|
|
||||||
for field_name, field in attrs["__annotations__"].items()
|
|
||||||
if issubclass(field, BaseField)
|
|
||||||
}
|
}
|
||||||
new_model.Meta.model_fields = model_fields
|
if potential_fields:
|
||||||
return new_model
|
warnings.warn(
|
||||||
|
"Using ormar.Fields as type Model annotation has been deprecated,"
|
||||||
|
" check documentation of current version",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
|
||||||
|
potential_fields.update(
|
||||||
|
{k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)}
|
||||||
|
)
|
||||||
|
for field_name, field in potential_fields.items():
|
||||||
|
if field.name is None:
|
||||||
|
field.name = field_name
|
||||||
|
attrs = populate_default_pydantic_field_value(field, field_name, attrs)
|
||||||
|
model_fields[field_name] = field
|
||||||
|
attrs["__annotations__"][field_name] = field.__type__
|
||||||
|
return attrs, model_fields
|
||||||
|
|
||||||
|
|
||||||
|
def extract_annotations_and_default_vals(attrs: dict) -> Tuple[Dict, Dict]:
|
||||||
|
key = "__annotations__"
|
||||||
|
attrs[key] = attrs.get(key, {})
|
||||||
|
attrs, model_fields = populate_pydantic_default_values(attrs)
|
||||||
|
return attrs, model_fields
|
||||||
|
|
||||||
|
|
||||||
def populate_meta_tablename_columns_and_pk(
|
def populate_meta_tablename_columns_and_pk(
|
||||||
@ -261,7 +268,7 @@ def populate_meta_sqlalchemy_table_if_required(
|
|||||||
def get_pydantic_base_orm_config() -> Type[BaseConfig]:
|
def get_pydantic_base_orm_config() -> Type[BaseConfig]:
|
||||||
class Config(BaseConfig):
|
class Config(BaseConfig):
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
arbitrary_types_allowed = True
|
# arbitrary_types_allowed = True
|
||||||
|
|
||||||
return Config
|
return Config
|
||||||
|
|
||||||
@ -305,7 +312,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
|||||||
) -> "ModelMetaclass":
|
) -> "ModelMetaclass":
|
||||||
attrs["Config"] = get_pydantic_base_orm_config()
|
attrs["Config"] = get_pydantic_base_orm_config()
|
||||||
attrs["__name__"] = name
|
attrs["__name__"] = name
|
||||||
attrs = extract_annotations_and_default_vals(attrs, bases)
|
attrs, model_fields = extract_annotations_and_default_vals(attrs)
|
||||||
new_model = super().__new__( # type: ignore
|
new_model = super().__new__( # type: ignore
|
||||||
mcs, name, bases, attrs
|
mcs, name, bases, attrs
|
||||||
)
|
)
|
||||||
@ -313,7 +320,8 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
|||||||
if hasattr(new_model, "Meta"):
|
if hasattr(new_model, "Meta"):
|
||||||
if not hasattr(new_model.Meta, "constraints"):
|
if not hasattr(new_model.Meta, "constraints"):
|
||||||
new_model.Meta.constraints = []
|
new_model.Meta.constraints = []
|
||||||
new_model = populate_meta_orm_model_fields(attrs, new_model)
|
if not hasattr(new_model.Meta, "model_fields"):
|
||||||
|
new_model.Meta.model_fields = model_fields
|
||||||
new_model = populate_meta_tablename_columns_and_pk(name, new_model)
|
new_model = populate_meta_tablename_columns_and_pk(name, new_model)
|
||||||
new_model = populate_meta_sqlalchemy_table_if_required(new_model)
|
new_model = populate_meta_sqlalchemy_table_if_required(new_model)
|
||||||
expand_reverse_relationships(new_model)
|
expand_reverse_relationships(new_model)
|
||||||
@ -322,7 +330,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
|||||||
if new_model.Meta.pkname not in attrs["__annotations__"]:
|
if new_model.Meta.pkname not in attrs["__annotations__"]:
|
||||||
field_name = new_model.Meta.pkname
|
field_name = new_model.Meta.pkname
|
||||||
field = Integer(name=field_name, primary_key=True)
|
field = Integer(name=field_name, primary_key=True)
|
||||||
attrs["__annotations__"][field_name] = field
|
attrs["__annotations__"][field_name] = Optional[int] # type: ignore
|
||||||
populate_default_pydantic_field_value(
|
populate_default_pydantic_field_value(
|
||||||
field, field_name, attrs # type: ignore
|
field, field_name, attrs # type: ignore
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import itertools
|
import itertools
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Type, TypeVar
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
import ormar.queryset # noqa I100
|
import ormar.queryset # noqa I100
|
||||||
from ormar.fields.many_to_many import ManyToManyField
|
from ormar.fields.many_to_many import ManyToManyField
|
||||||
from ormar.models import NewBaseModel # noqa I100
|
from ormar.models import NewBaseModel # noqa I100
|
||||||
|
from ormar.models.metaclass import ModelMeta
|
||||||
|
|
||||||
|
|
||||||
def group_related_list(list_: List) -> Dict:
|
def group_related_list(list_: List) -> Dict:
|
||||||
@ -23,18 +24,34 @@ def group_related_list(list_: List) -> Dict:
|
|||||||
return test_dict
|
return test_dict
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma nocover
|
||||||
|
from ormar import QuerySet
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="Model")
|
||||||
|
|
||||||
|
|
||||||
class Model(NewBaseModel):
|
class Model(NewBaseModel):
|
||||||
__abstract__ = False
|
__abstract__ = False
|
||||||
|
if TYPE_CHECKING: # pragma nocover
|
||||||
|
Meta: ModelMeta
|
||||||
|
objects: "QuerySet"
|
||||||
|
|
||||||
|
def __repr__(self) -> str: # pragma nocover
|
||||||
|
attrs_to_include = ["tablename", "columns", "pkname"]
|
||||||
|
_repr = {k: v for k, v in self.Meta.model_fields.items()}
|
||||||
|
for atr in attrs_to_include:
|
||||||
|
_repr[atr] = getattr(self.Meta, atr)
|
||||||
|
return f"{self.__class__.__name__}({str(_repr)})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row( # noqa CCR001
|
def from_row( # noqa CCR001
|
||||||
cls,
|
cls: Type[T],
|
||||||
row: sqlalchemy.engine.ResultProxy,
|
row: sqlalchemy.engine.ResultProxy,
|
||||||
select_related: List = None,
|
select_related: List = None,
|
||||||
related_models: Any = None,
|
related_models: Any = None,
|
||||||
previous_table: str = None,
|
previous_table: str = None,
|
||||||
fields: List = None,
|
fields: List = None,
|
||||||
) -> Optional["Model"]:
|
) -> Optional[T]:
|
||||||
|
|
||||||
item: Dict[str, Any] = {}
|
item: Dict[str, Any] = {}
|
||||||
select_related = select_related or []
|
select_related = select_related or []
|
||||||
@ -66,7 +83,9 @@ class Model(NewBaseModel):
|
|||||||
item, row, table_prefix, fields, nested=table_prefix != ""
|
item, row, table_prefix, fields, nested=table_prefix != ""
|
||||||
)
|
)
|
||||||
|
|
||||||
instance = cls(**item) if item.get(cls.Meta.pkname, None) is not None else None
|
instance: Optional[T] = cls(**item) if item.get(
|
||||||
|
cls.Meta.pkname, None
|
||||||
|
) is not None else None
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -124,7 +143,7 @@ class Model(NewBaseModel):
|
|||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
async def save(self) -> "Model":
|
async def save(self: T) -> T:
|
||||||
self_fields = self._extract_model_db_fields()
|
self_fields = self._extract_model_db_fields()
|
||||||
|
|
||||||
if not self.pk and self.Meta.model_fields[self.Meta.pkname].autoincrement:
|
if not self.pk and self.Meta.model_fields[self.Meta.pkname].autoincrement:
|
||||||
@ -137,7 +156,7 @@ class Model(NewBaseModel):
|
|||||||
setattr(self, self.Meta.pkname, item_id)
|
setattr(self, self.Meta.pkname, item_id)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def update(self, **kwargs: Any) -> "Model":
|
async def update(self: T, **kwargs: Any) -> T:
|
||||||
if kwargs:
|
if kwargs:
|
||||||
new_values = {**self.dict(), **kwargs}
|
new_values = {**self.dict(), **kwargs}
|
||||||
self.from_dict(new_values)
|
self.from_dict(new_values)
|
||||||
@ -151,13 +170,13 @@ class Model(NewBaseModel):
|
|||||||
await self.Meta.database.execute(expr)
|
await self.Meta.database.execute(expr)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def delete(self) -> int:
|
async def delete(self: T) -> int:
|
||||||
expr = self.Meta.table.delete()
|
expr = self.Meta.table.delete()
|
||||||
expr = expr.where(self.pk_column == (getattr(self, self.Meta.pkname)))
|
expr = expr.where(self.pk_column == (getattr(self, self.Meta.pkname)))
|
||||||
result = await self.Meta.database.execute(expr)
|
result = await self.Meta.database.execute(expr)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def load(self) -> "Model":
|
async def load(self: T) -> T:
|
||||||
expr = self.Meta.table.select().where(self.pk_column == self.pk)
|
expr = self.Meta.table.select().where(self.pk_column == self.pk)
|
||||||
row = await self.Meta.database.fetch_one(expr)
|
row = await self.Meta.database.fetch_one(expr)
|
||||||
if not row: # pragma nocover
|
if not row: # pragma nocover
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import Dict, List, Set, TYPE_CHECKING, Type, TypeVar, Union
|
from typing import Dict, List, Sequence, Set, TYPE_CHECKING, Type, TypeVar, Union
|
||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
from ormar.exceptions import RelationshipInstanceError
|
from ormar.exceptions import RelationshipInstanceError
|
||||||
@ -11,6 +11,8 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
from ormar import Model
|
from ormar import Model
|
||||||
from ormar.models import NewBaseModel
|
from ormar.models import NewBaseModel
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
Field = TypeVar("Field", bound=BaseField)
|
Field = TypeVar("Field", bound=BaseField)
|
||||||
|
|
||||||
|
|
||||||
@ -135,7 +137,7 @@ class ModelTableProxy:
|
|||||||
if field.to == related.__class__ or field.to.Meta == related.Meta:
|
if field.to == related.__class__ or field.to.Meta == related.Meta:
|
||||||
return name
|
return name
|
||||||
# fallback for not registered relation
|
# fallback for not registered relation
|
||||||
if register_missing:
|
if register_missing: # pragma nocover
|
||||||
expand_reverse_relationships(related.__class__) # type: ignore
|
expand_reverse_relationships(related.__class__) # type: ignore
|
||||||
return ModelTableProxy.resolve_relation_name(
|
return ModelTableProxy.resolve_relation_name(
|
||||||
item, related, register_missing=False
|
item, related, register_missing=False
|
||||||
@ -177,7 +179,7 @@ class ModelTableProxy:
|
|||||||
return new_kwargs
|
return new_kwargs
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def merge_instances_list(cls, result_rows: List["Model"]) -> List["Model"]:
|
def merge_instances_list(cls, result_rows: Sequence["Model"]) -> Sequence["Model"]:
|
||||||
merged_rows: List["Model"] = []
|
merged_rows: List["Model"] = []
|
||||||
for index, model in enumerate(result_rows):
|
for index, model in enumerate(result_rows):
|
||||||
if index > 0 and model is not None and model.pk == merged_rows[-1].pk:
|
if index > 0 and model is not None and model.pk == merged_rows[-1].pk:
|
||||||
|
|||||||
@ -5,11 +5,12 @@ from typing import (
|
|||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
|
||||||
Mapping,
|
Mapping,
|
||||||
Optional,
|
Optional,
|
||||||
|
Sequence,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Type,
|
Type,
|
||||||
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +28,9 @@ from ormar.relations.alias_manager import AliasManager
|
|||||||
from ormar.relations.relation_manager import RelationsManager
|
from ormar.relations.relation_manager import RelationsManager
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
from ormar.models.model import Model
|
from ormar import Model
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
IntStr = Union[int, str]
|
IntStr = Union[int, str]
|
||||||
DictStrAny = Dict[str, Any]
|
DictStrAny = Dict[str, Any]
|
||||||
@ -52,7 +55,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
Meta: ModelMeta
|
Meta: ModelMeta
|
||||||
|
|
||||||
# noinspection PyMissingConstructor
|
# noinspection PyMissingConstructor
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore
|
||||||
|
|
||||||
object.__setattr__(self, "_orm_id", uuid.uuid4().hex)
|
object.__setattr__(self, "_orm_id", uuid.uuid4().hex)
|
||||||
object.__setattr__(self, "_orm_saved", False)
|
object.__setattr__(self, "_orm_saved", False)
|
||||||
@ -73,7 +76,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
if "pk" in kwargs:
|
if "pk" in kwargs:
|
||||||
kwargs[self.Meta.pkname] = kwargs.pop("pk")
|
kwargs[self.Meta.pkname] = kwargs.pop("pk")
|
||||||
# build the models to set them and validate but don't register
|
# build the models to set them and validate but don't register
|
||||||
kwargs = {
|
new_kwargs = {
|
||||||
k: self._convert_json(
|
k: self._convert_json(
|
||||||
k,
|
k,
|
||||||
self.Meta.model_fields[k].expand_relationship(
|
self.Meta.model_fields[k].expand_relationship(
|
||||||
@ -85,7 +88,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
}
|
}
|
||||||
|
|
||||||
values, fields_set, validation_error = pydantic.validate_model(
|
values, fields_set, validation_error = pydantic.validate_model(
|
||||||
self, kwargs # type: ignore
|
self, new_kwargs # type: ignore
|
||||||
)
|
)
|
||||||
if validation_error and not pk_only:
|
if validation_error and not pk_only:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
@ -96,7 +99,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
# register the columns models after initialization
|
# register the columns models after initialization
|
||||||
for related in self.extract_related_names():
|
for related in self.extract_related_names():
|
||||||
self.Meta.model_fields[related].expand_relationship(
|
self.Meta.model_fields[related].expand_relationship(
|
||||||
kwargs.get(related), self, to_register=True
|
new_kwargs.get(related), self, to_register=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __setattr__(self, name: str, value: Any) -> None: # noqa CCR001
|
def __setattr__(self, name: str, value: Any) -> None: # noqa CCR001
|
||||||
@ -133,7 +136,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
|
|
||||||
def _extract_related_model_instead_of_field(
|
def _extract_related_model_instead_of_field(
|
||||||
self, item: str
|
self, item: str
|
||||||
) -> Optional[Union["Model", List["Model"]]]:
|
) -> Optional[Union["T", Sequence["T"]]]:
|
||||||
alias = self.get_column_alias(item)
|
alias = self.get_column_alias(item)
|
||||||
if alias in self._orm:
|
if alias in self._orm:
|
||||||
return self._orm.get(alias)
|
return self._orm.get(alias)
|
||||||
@ -170,7 +173,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
def db_backend_name(cls) -> str:
|
def db_backend_name(cls) -> str:
|
||||||
return cls.Meta.database._backend._dialect.name
|
return cls.Meta.database._backend._dialect.name
|
||||||
|
|
||||||
def remove(self, name: "Model") -> None:
|
def remove(self, name: "T") -> None:
|
||||||
self._orm.remove_parent(self, name)
|
self._orm.remove_parent(self, name)
|
||||||
|
|
||||||
def dict( # noqa A003
|
def dict( # noqa A003
|
||||||
|
|||||||
4
ormar/protocols/__init__.py
Normal file
4
ormar/protocols/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from ormar.protocols.queryset_protocol import QuerySetProtocol
|
||||||
|
from ormar.protocols.relation_protocol import RelationProtocol
|
||||||
|
|
||||||
|
__all__ = ["QuerySetProtocol", "RelationProtocol"]
|
||||||
46
ormar/protocols/queryset_protocol.py
Normal file
46
ormar/protocols/queryset_protocol.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Protocol
|
||||||
|
except ImportError: # pragma: nocover
|
||||||
|
from typing_extensions import Protocol # type: ignore
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # noqa: C901; #pragma nocover
|
||||||
|
from ormar import QuerySet, Model
|
||||||
|
|
||||||
|
|
||||||
|
class QuerySetProtocol(Protocol): # pragma: nocover
|
||||||
|
def filter(self, **kwargs: Any) -> "QuerySet": # noqa: A003, A001
|
||||||
|
...
|
||||||
|
|
||||||
|
def select_related(self, related: Union[List, str]) -> "QuerySet":
|
||||||
|
...
|
||||||
|
|
||||||
|
async def exists(self) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
async def count(self) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
async def clear(self) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
def limit(self, limit_count: int) -> "QuerySet":
|
||||||
|
...
|
||||||
|
|
||||||
|
def offset(self, offset: int) -> "QuerySet":
|
||||||
|
...
|
||||||
|
|
||||||
|
async def first(self, **kwargs: Any) -> "Model":
|
||||||
|
...
|
||||||
|
|
||||||
|
async def get(self, **kwargs: Any) -> "Model":
|
||||||
|
...
|
||||||
|
|
||||||
|
async def all( # noqa: A003, A001
|
||||||
|
self, **kwargs: Any
|
||||||
|
) -> Sequence[Optional["Model"]]:
|
||||||
|
...
|
||||||
|
|
||||||
|
async def create(self, **kwargs: Any) -> "Model":
|
||||||
|
...
|
||||||
17
ormar/protocols/relation_protocol.py
Normal file
17
ormar/protocols/relation_protocol.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from typing import TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Protocol
|
||||||
|
except ImportError: # pragma: nocover
|
||||||
|
from typing_extensions import Protocol # type: ignore
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: nocover
|
||||||
|
from ormar import Model
|
||||||
|
|
||||||
|
|
||||||
|
class RelationProtocol(Protocol): # pragma: nocover
|
||||||
|
def add(self, child: "Model") -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def remove(self, child: Union["Model", Type["Model"]]) -> None:
|
||||||
|
...
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -39,7 +39,7 @@ class QuerySet:
|
|||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self,
|
self,
|
||||||
instance: Union["QuerySet", "QuerysetProxy"],
|
instance: Optional[Union["QuerySet", "QuerysetProxy"]],
|
||||||
owner: Union[Type["Model"], Type["QuerysetProxy"]],
|
owner: Union[Type["Model"], Type["QuerysetProxy"]],
|
||||||
) -> "QuerySet":
|
) -> "QuerySet":
|
||||||
if issubclass(owner, ormar.Model):
|
if issubclass(owner, ormar.Model):
|
||||||
@ -59,7 +59,7 @@ class QuerySet:
|
|||||||
raise ValueError("Model class of QuerySet is not initialized")
|
raise ValueError("Model class of QuerySet is not initialized")
|
||||||
return self.model_cls
|
return self.model_cls
|
||||||
|
|
||||||
def _process_query_result_rows(self, rows: List) -> List[Optional["Model"]]:
|
def _process_query_result_rows(self, rows: List) -> Sequence[Optional["Model"]]:
|
||||||
result_rows = [
|
result_rows = [
|
||||||
self.model.from_row(
|
self.model.from_row(
|
||||||
row, select_related=self._select_related, fields=self._columns
|
row, select_related=self._select_related, fields=self._columns
|
||||||
@ -87,7 +87,7 @@ class QuerySet:
|
|||||||
return new_kwargs
|
return new_kwargs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_single_result_rows_count(rows: List[Optional["Model"]]) -> None:
|
def check_single_result_rows_count(rows: Sequence[Optional["Model"]]) -> None:
|
||||||
if not rows or rows[0] is None:
|
if not rows or rows[0] is None:
|
||||||
raise NoMatch()
|
raise NoMatch()
|
||||||
if len(rows) > 1:
|
if len(rows) > 1:
|
||||||
@ -267,7 +267,7 @@ class QuerySet:
|
|||||||
model = await self.get(pk=kwargs[pk_name])
|
model = await self.get(pk=kwargs[pk_name])
|
||||||
return await model.update(**kwargs)
|
return await model.update(**kwargs)
|
||||||
|
|
||||||
async def all(self, **kwargs: Any) -> List[Optional["Model"]]: # noqa: A003
|
async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003
|
||||||
if kwargs:
|
if kwargs:
|
||||||
return await self.filter(**kwargs).all()
|
return await self.filter(**kwargs).all()
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, List, Optional, TYPE_CHECKING, Union
|
from typing import Any, List, Optional, Sequence, TYPE_CHECKING, TypeVar, Union
|
||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
|
|
||||||
@ -7,8 +7,10 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
from ormar.models import Model
|
from ormar.models import Model
|
||||||
from ormar.queryset import QuerySet
|
from ormar.queryset import QuerySet
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
class QuerysetProxy:
|
|
||||||
|
class QuerysetProxy(ormar.QuerySetProtocol):
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
relation: "Relation"
|
relation: "Relation"
|
||||||
|
|
||||||
@ -26,27 +28,28 @@ class QuerysetProxy:
|
|||||||
def queryset(self, value: "QuerySet") -> None:
|
def queryset(self, value: "QuerySet") -> None:
|
||||||
self._queryset = value
|
self._queryset = value
|
||||||
|
|
||||||
def _assign_child_to_parent(self, child: Optional["Model"]) -> None:
|
def _assign_child_to_parent(self, child: Optional["T"]) -> None:
|
||||||
if child:
|
if child:
|
||||||
owner = self.relation._owner
|
owner = self.relation._owner
|
||||||
rel_name = owner.resolve_relation_name(owner, child)
|
rel_name = owner.resolve_relation_name(owner, child)
|
||||||
setattr(owner, rel_name, child)
|
setattr(owner, rel_name, child)
|
||||||
|
|
||||||
def _register_related(self, child: Union["Model", List[Optional["Model"]]]) -> None:
|
def _register_related(self, child: Union["T", Sequence[Optional["T"]]]) -> None:
|
||||||
if isinstance(child, list):
|
if isinstance(child, list):
|
||||||
for subchild in child:
|
for subchild in child:
|
||||||
self._assign_child_to_parent(subchild)
|
self._assign_child_to_parent(subchild)
|
||||||
else:
|
else:
|
||||||
|
assert isinstance(child, ormar.Model)
|
||||||
self._assign_child_to_parent(child)
|
self._assign_child_to_parent(child)
|
||||||
|
|
||||||
async def create_through_instance(self, child: "Model") -> None:
|
async def create_through_instance(self, child: "T") -> None:
|
||||||
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
||||||
owner_column = self.relation._owner.get_name()
|
owner_column = self.relation._owner.get_name()
|
||||||
child_column = child.get_name()
|
child_column = child.get_name()
|
||||||
kwargs = {owner_column: self.relation._owner, child_column: child}
|
kwargs = {owner_column: self.relation._owner, child_column: child}
|
||||||
await queryset.create(**kwargs)
|
await queryset.create(**kwargs)
|
||||||
|
|
||||||
async def delete_through_instance(self, child: "Model") -> None:
|
async def delete_through_instance(self, child: "T") -> None:
|
||||||
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
||||||
owner_column = self.relation._owner.get_name()
|
owner_column = self.relation._owner.get_name()
|
||||||
child_column = child.get_name()
|
child_column = child.get_name()
|
||||||
@ -88,7 +91,7 @@ class QuerysetProxy:
|
|||||||
self._register_related(get)
|
self._register_related(get)
|
||||||
return get
|
return get
|
||||||
|
|
||||||
async def all(self, **kwargs: Any) -> List[Optional["Model"]]: # noqa: A003
|
async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003
|
||||||
all_items = await self.queryset.all(**kwargs)
|
all_items = await self.queryset.all(**kwargs)
|
||||||
self._register_related(all_items)
|
self._register_related(all_items)
|
||||||
return all_items
|
return all_items
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional, TYPE_CHECKING, Type, Union
|
from typing import List, Optional, TYPE_CHECKING, Type, TypeVar, Union
|
||||||
|
|
||||||
import ormar # noqa I100
|
import ormar # noqa I100
|
||||||
from ormar.exceptions import RelationshipInstanceError # noqa I100
|
from ormar.exceptions import RelationshipInstanceError # noqa I100
|
||||||
@ -11,6 +11,8 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
from ormar.relations import RelationsManager
|
from ormar.relations import RelationsManager
|
||||||
from ormar.models import NewBaseModel
|
from ormar.models import NewBaseModel
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
|
|
||||||
class RelationType(Enum):
|
class RelationType(Enum):
|
||||||
PRIMARY = 1
|
PRIMARY = 1
|
||||||
@ -23,15 +25,15 @@ class Relation:
|
|||||||
self,
|
self,
|
||||||
manager: "RelationsManager",
|
manager: "RelationsManager",
|
||||||
type_: RelationType,
|
type_: RelationType,
|
||||||
to: Type["Model"],
|
to: Type["T"],
|
||||||
through: Type["Model"] = None,
|
through: Type["T"] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self._owner: "Model" = manager.owner
|
self._owner: "Model" = manager.owner
|
||||||
self._type: RelationType = type_
|
self._type: RelationType = type_
|
||||||
self.to: Type["Model"] = to
|
self.to: Type["T"] = to
|
||||||
self.through: Optional[Type["Model"]] = through
|
self.through: Optional[Type["T"]] = through
|
||||||
self.related_models: Optional[Union[RelationProxy, "Model"]] = (
|
self.related_models: Optional[Union[RelationProxy, "T"]] = (
|
||||||
RelationProxy(relation=self)
|
RelationProxy(relation=self)
|
||||||
if type_ in (RelationType.REVERSE, RelationType.MULTIPLE)
|
if type_ in (RelationType.REVERSE, RelationType.MULTIPLE)
|
||||||
else None
|
else None
|
||||||
@ -50,7 +52,7 @@ class Relation:
|
|||||||
self.related_models.pop(ind)
|
self.related_models.pop(ind)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add(self, child: "Model") -> None:
|
def add(self, child: "T") -> None:
|
||||||
relation_name = self._owner.resolve_relation_name(self._owner, child)
|
relation_name = self._owner.resolve_relation_name(self._owner, child)
|
||||||
if self._type == RelationType.PRIMARY:
|
if self._type == RelationType.PRIMARY:
|
||||||
self.related_models = child
|
self.related_models = child
|
||||||
@ -77,7 +79,7 @@ class Relation:
|
|||||||
self.related_models.pop(position) # type: ignore
|
self.related_models.pop(position) # type: ignore
|
||||||
del self._owner.__dict__[relation_name][position]
|
del self._owner.__dict__[relation_name][position]
|
||||||
|
|
||||||
def get(self) -> Optional[Union[List["Model"], "Model"]]:
|
def get(self) -> Optional[Union[List["T"], "T"]]:
|
||||||
return self.related_models
|
return self.related_models
|
||||||
|
|
||||||
def __repr__(self) -> str: # pragma no cover
|
def __repr__(self) -> str: # pragma no cover
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union
|
from typing import Dict, List, Optional, Sequence, TYPE_CHECKING, Type, TypeVar, Union
|
||||||
from weakref import proxy
|
from weakref import proxy
|
||||||
|
|
||||||
from ormar.fields import BaseField
|
from ormar.fields import BaseField
|
||||||
@ -14,6 +14,8 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
from ormar import Model
|
from ormar import Model
|
||||||
from ormar.models import NewBaseModel
|
from ormar.models import NewBaseModel
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
|
|
||||||
class RelationsManager:
|
class RelationsManager:
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -46,7 +48,7 @@ class RelationsManager:
|
|||||||
def __contains__(self, item: str) -> bool:
|
def __contains__(self, item: str) -> bool:
|
||||||
return item in self._related_names
|
return item in self._related_names
|
||||||
|
|
||||||
def get(self, name: str) -> Optional[Union[List["Model"], "Model"]]:
|
def get(self, name: str) -> Optional[Union["T", Sequence["T"]]]:
|
||||||
relation = self._relations.get(name, None)
|
relation = self._relations.get(name, None)
|
||||||
if relation is not None:
|
if relation is not None:
|
||||||
return relation.get()
|
return relation.get()
|
||||||
|
|||||||
@ -72,6 +72,6 @@ class RelationProxy(list):
|
|||||||
if self.relation._type == ormar.RelationType.MULTIPLE:
|
if self.relation._type == ormar.RelationType.MULTIPLE:
|
||||||
await self.queryset_proxy.create_through_instance(item)
|
await self.queryset_proxy.create_through_instance(item)
|
||||||
rel_name = item.resolve_relation_name(item, self._owner)
|
rel_name = item.resolve_relation_name(item, self._owner)
|
||||||
if rel_name not in item._orm:
|
if rel_name not in item._orm: # pragma nocover
|
||||||
item._orm._add_relation(item.Meta.model_fields[rel_name])
|
item._orm._add_relation(item.Meta.model_fields[rel_name])
|
||||||
setattr(item, rel_name, self._owner)
|
setattr(item, rel_name, self._owner)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ databases[postgresql]
|
|||||||
databases[mysql]
|
databases[mysql]
|
||||||
pydantic
|
pydantic
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
typing_extensions
|
||||||
|
|
||||||
# Async database drivers
|
# Async database drivers
|
||||||
aiomysql
|
aiomysql
|
||||||
|
|||||||
2
setup.py
2
setup.py
@ -51,7 +51,7 @@ setup(
|
|||||||
packages=get_packages(PACKAGE),
|
packages=get_packages(PACKAGE),
|
||||||
package_data={PACKAGE: ["py.typed"]},
|
package_data={PACKAGE: ["py.typed"]},
|
||||||
data_files=[("", ["LICENSE.md"])],
|
data_files=[("", ["LICENSE.md"])],
|
||||||
install_requires=["databases", "pydantic>=1.5", "sqlalchemy"],
|
install_requires=["databases", "pydantic>=1.5", "sqlalchemy", "typing_extensions"],
|
||||||
extras_require={
|
extras_require={
|
||||||
"postgresql": ["asyncpg", "psycopg2"],
|
"postgresql": ["asyncpg", "psycopg2"],
|
||||||
"mysql": ["aiomysql", "pymysql"],
|
"mysql": ["aiomysql", "pymysql"],
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional, Union, List
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -15,10 +17,10 @@ class Child(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(name="child_id", primary_key=True)
|
id: int = ormar.Integer(name="child_id", primary_key=True)
|
||||||
first_name: ormar.String(name="fname", max_length=100)
|
first_name: str = ormar.String(name="fname", max_length=100)
|
||||||
last_name: ormar.String(name="lname", max_length=100)
|
last_name: str = ormar.String(name="lname", max_length=100)
|
||||||
born_year: ormar.Integer(name="year_born", nullable=True)
|
born_year: int = ormar.Integer(name="year_born", nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class ArtistChildren(ormar.Model):
|
class ArtistChildren(ormar.Model):
|
||||||
@ -34,11 +36,13 @@ class Artist(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(name="artist_id", primary_key=True)
|
id: int = ormar.Integer(name="artist_id", primary_key=True)
|
||||||
first_name: ormar.String(name="fname", max_length=100)
|
first_name: str = ormar.String(name="fname", max_length=100)
|
||||||
last_name: ormar.String(name="lname", max_length=100)
|
last_name: str = ormar.String(name="lname", max_length=100)
|
||||||
born_year: ormar.Integer(name="year")
|
born_year: int = ormar.Integer(name="year")
|
||||||
children: ormar.ManyToMany(Child, through=ArtistChildren)
|
children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany(
|
||||||
|
Child, through=ArtistChildren
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Album(ormar.Model):
|
class Album(ormar.Model):
|
||||||
@ -47,9 +51,9 @@ class Album(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(name="album_id", primary_key=True)
|
id: int = ormar.Integer(name="album_id", primary_key=True)
|
||||||
name: ormar.String(name="album_name", max_length=100)
|
name: str = ormar.String(name="album_name", max_length=100)
|
||||||
artist: ormar.ForeignKey(Artist, name="artist_id")
|
artist: Optional[Artist] = ormar.ForeignKey(Artist, name="artist_id")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
|
import pydantic
|
||||||
import pytest
|
import pytest
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@ -22,14 +23,14 @@ class Example(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=200, default="aaa")
|
name: str = ormar.String(max_length=200, default="aaa")
|
||||||
created: ormar.DateTime(default=datetime.datetime.now)
|
created: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||||
created_day: ormar.Date(default=datetime.date.today)
|
created_day: datetime.date = ormar.Date(default=datetime.date.today)
|
||||||
created_time: ormar.Time(default=time)
|
created_time: datetime.time = ormar.Time(default=time)
|
||||||
description: ormar.Text(nullable=True)
|
description: str = ormar.Text(nullable=True)
|
||||||
value: ormar.Float(nullable=True)
|
value: float = ormar.Float(nullable=True)
|
||||||
data: ormar.JSON(default={})
|
data: pydantic.Json = ormar.JSON(default={})
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
0
tests/test_docs/__init__.py
Normal file
0
tests/test_docs/__init__.py
Normal file
@ -1,4 +1,4 @@
|
|||||||
from typing import List
|
from typing import List, Union, Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -38,8 +38,8 @@ class Category(ormar.Model):
|
|||||||
class Meta(LocalMeta):
|
class Meta(LocalMeta):
|
||||||
tablename = "categories"
|
tablename = "categories"
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class ItemsXCategories(ormar.Model):
|
class ItemsXCategories(ormar.Model):
|
||||||
@ -51,9 +51,9 @@ class Item(ormar.Model):
|
|||||||
class Meta(LocalMeta):
|
class Meta(LocalMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
categories: ormar.ManyToMany(Category, through=ItemsXCategories)
|
categories = ormar.ManyToMany(Category, through=ItemsXCategories)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
@ -77,7 +77,7 @@ async def create_item(item: Item):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/items/add_category/", response_model=Item)
|
@app.post("/items/add_category/", response_model=Item)
|
||||||
async def create_item(item: Item, category: Category):
|
async def add_item_category(item: Item, category: Category):
|
||||||
await item.categories.add(category)
|
await item.categories.add(category)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@ -125,7 +125,9 @@ def test_all_endpoints():
|
|||||||
|
|
||||||
def test_schema_modification():
|
def test_schema_modification():
|
||||||
schema = Item.schema()
|
schema = Item.schema()
|
||||||
assert schema["properties"]["categories"]["type"] == "array"
|
assert any(
|
||||||
|
x.get("type") == "array" for x in schema["properties"]["categories"]["anyOf"]
|
||||||
|
)
|
||||||
assert schema["properties"]["categories"]["title"] == "Categories"
|
assert schema["properties"]["categories"]["title"] == "Categories"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
@ -18,8 +20,8 @@ class Category(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Item(ormar.Model):
|
class Item(ormar.Model):
|
||||||
@ -28,9 +30,9 @@ class Item(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/items/", response_model=Item)
|
@app.post("/items/", response_model=Item)
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -16,8 +18,8 @@ class Album(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Track(ormar.Model):
|
class Track(ormar.Model):
|
||||||
@ -26,10 +28,10 @@ class Track(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
album: ormar.ForeignKey(Album)
|
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||||
title: ormar.String(max_length=100)
|
title: str = ormar.String(max_length=100)
|
||||||
position: ormar.Integer()
|
position: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
class Cover(ormar.Model):
|
class Cover(ormar.Model):
|
||||||
@ -38,9 +40,9 @@ class Cover(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
album: ormar.ForeignKey(Album, related_name="cover_pictures")
|
album: Optional[Album] = ormar.ForeignKey(Album, related_name="cover_pictures")
|
||||||
title: ormar.String(max_length=100)
|
title: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Organisation(ormar.Model):
|
class Organisation(ormar.Model):
|
||||||
@ -49,8 +51,12 @@ class Organisation(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
ident: ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"])
|
ident: str = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"])
|
||||||
|
|
||||||
|
|
||||||
|
class Organization(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Team(ormar.Model):
|
class Team(ormar.Model):
|
||||||
@ -59,9 +65,9 @@ class Team(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
org: ormar.ForeignKey(Organisation)
|
org: Optional[Organisation] = ormar.ForeignKey(Organisation)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Member(ormar.Model):
|
class Member(ormar.Model):
|
||||||
@ -70,9 +76,9 @@ class Member(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
team: ormar.ForeignKey(Team)
|
team: Optional[Team] = ormar.ForeignKey(Team)
|
||||||
email: ormar.String(max_length=100)
|
email: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import List, Union, Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -18,9 +19,9 @@ class Author(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
first_name: ormar.String(max_length=80)
|
first_name: str = ormar.String(max_length=80)
|
||||||
last_name: ormar.String(max_length=80)
|
last_name: str = ormar.String(max_length=80)
|
||||||
|
|
||||||
|
|
||||||
class Category(ormar.Model):
|
class Category(ormar.Model):
|
||||||
@ -29,8 +30,8 @@ class Category(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=40)
|
name: str = ormar.String(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
class PostCategory(ormar.Model):
|
class PostCategory(ormar.Model):
|
||||||
@ -46,10 +47,12 @@ class Post(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
title: ormar.String(max_length=200)
|
title: str = ormar.String(max_length=200)
|
||||||
categories: ormar.ManyToMany(Category, through=PostCategory)
|
categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
||||||
author: ormar.ForeignKey(Author)
|
Category, through=PostCategory
|
||||||
|
)
|
||||||
|
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
|
# type: ignore
|
||||||
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
|
||||||
import pydantic
|
import pydantic
|
||||||
import pytest
|
import pytest
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
import typing
|
||||||
|
|
||||||
import ormar.fields as fields
|
import ormar
|
||||||
from ormar.exceptions import ModelDefinitionError
|
from ormar.exceptions import ModelDefinitionError
|
||||||
from ormar.models import Model
|
from ormar.models import Model
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
metadata = sqlalchemy.MetaData()
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
@ -17,18 +21,18 @@ class ExampleModel(Model):
|
|||||||
tablename = "example"
|
tablename = "example"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
test: fields.Integer(primary_key=True)
|
test: int = ormar.Integer(primary_key=True)
|
||||||
test_string: fields.String(max_length=250)
|
test_string: str = ormar.String(max_length=250)
|
||||||
test_text: fields.Text(default="")
|
test_text: str = ormar.Text(default="")
|
||||||
test_bool: fields.Boolean(nullable=False)
|
test_bool: bool = ormar.Boolean(nullable=False)
|
||||||
test_float: fields.Float() = None
|
test_float: ormar.Float() = None # type: ignore
|
||||||
test_datetime: fields.DateTime(default=datetime.datetime.now)
|
test_datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||||
test_date: fields.Date(default=datetime.date.today)
|
test_date = ormar.Date(default=datetime.date.today)
|
||||||
test_time: fields.Time(default=datetime.time)
|
test_time = ormar.Time(default=datetime.time)
|
||||||
test_json: fields.JSON(default={})
|
test_json = ormar.JSON(default={})
|
||||||
test_bigint: fields.BigInteger(default=0)
|
test_bigint: int = ormar.BigInteger(default=0)
|
||||||
test_decimal: fields.Decimal(scale=10, precision=2)
|
test_decimal = ormar.Decimal(scale=2, precision=10)
|
||||||
test_decimal2: fields.Decimal(max_digits=10, decimal_places=2)
|
test_decimal2 = ormar.Decimal(max_digits=10, decimal_places=2)
|
||||||
|
|
||||||
|
|
||||||
fields_to_check = [
|
fields_to_check = [
|
||||||
@ -46,11 +50,26 @@ fields_to_check = [
|
|||||||
|
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "example2"
|
tablename = "examples"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
test: fields.Integer(primary_key=True)
|
test: int = ormar.Integer(primary_key=True)
|
||||||
test_string: fields.String(max_length=250)
|
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()
|
@pytest.fixture()
|
||||||
@ -117,29 +136,33 @@ def test_sqlalchemy_table_is_created(example):
|
|||||||
assert all([field in example.Meta.table.columns for field in fields_to_check])
|
assert all([field in example.Meta.table.columns for field in fields_to_check])
|
||||||
|
|
||||||
|
|
||||||
def test_no_pk_in_model_definition():
|
@typing.no_type_check
|
||||||
with pytest.raises(ModelDefinitionError):
|
def test_no_pk_in_model_definition(): # type: ignore
|
||||||
|
with pytest.raises(ModelDefinitionError): # type: ignore
|
||||||
|
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model): # type: ignore
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "example3"
|
tablename = "example2"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
test_string: fields.String(max_length=250)
|
test_string: str = ormar.String(max_length=250) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
def test_two_pks_in_model_definition():
|
def test_two_pks_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "example3"
|
tablename = "example3"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
id: fields.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
test_string: fields.String(max_length=250, 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():
|
def test_setting_pk_column_as_pydantic_only_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
@ -148,9 +171,10 @@ def test_setting_pk_column_as_pydantic_only_in_model_definition():
|
|||||||
tablename = "example4"
|
tablename = "example4"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
test: fields.Integer(primary_key=True, pydantic_only=True)
|
test: int = ormar.Integer(primary_key=True, pydantic_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
def test_decimal_error_in_model_definition():
|
def test_decimal_error_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
@ -159,9 +183,10 @@ def test_decimal_error_in_model_definition():
|
|||||||
tablename = "example5"
|
tablename = "example5"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
test: fields.Decimal(primary_key=True)
|
test: decimal.Decimal = ormar.Decimal(primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
def test_string_error_in_model_definition():
|
def test_string_error_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
@ -170,9 +195,10 @@ def test_string_error_in_model_definition():
|
|||||||
tablename = "example6"
|
tablename = "example6"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
test: fields.String(primary_key=True)
|
test: str = ormar.String(primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
def test_json_conversion_in_model():
|
def test_json_conversion_in_model():
|
||||||
with pytest.raises(pydantic.ValidationError):
|
with pytest.raises(pydantic.ValidationError):
|
||||||
ExampleModel(
|
ExampleModel(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
import datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
@ -22,8 +22,8 @@ class JsonSample(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
test_json: ormar.JSON(nullable=True)
|
test_json = ormar.JSON(nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class UUIDSample(ormar.Model):
|
class UUIDSample(ormar.Model):
|
||||||
@ -32,8 +32,8 @@ class UUIDSample(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.UUID(primary_key=True, default=uuid.uuid4)
|
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4)
|
||||||
test_text: ormar.Text()
|
test_text: str = ormar.Text()
|
||||||
|
|
||||||
|
|
||||||
class User(ormar.Model):
|
class User(ormar.Model):
|
||||||
@ -42,8 +42,8 @@ class User(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100, default="")
|
name: str = ormar.String(max_length=100, default="")
|
||||||
|
|
||||||
|
|
||||||
class Product(ormar.Model):
|
class Product(ormar.Model):
|
||||||
@ -52,11 +52,11 @@ class Product(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
rating: ormar.Integer(minimum=1, maximum=5)
|
rating: int = ormar.Integer(minimum=1, maximum=5)
|
||||||
in_stock: ormar.Boolean(default=False)
|
in_stock: bool = ormar.Boolean(default=False)
|
||||||
last_delivery: ormar.Date(default=datetime.now)
|
last_delivery: datetime.date = ormar.Date(default=datetime.datetime.now)
|
||||||
|
|
||||||
|
|
||||||
country_name_choices = ("Canada", "Algeria", "United States")
|
country_name_choices = ("Canada", "Algeria", "United States")
|
||||||
@ -70,12 +70,12 @@ class Country(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(
|
name: str = ormar.String(
|
||||||
max_length=9, choices=country_name_choices, default="Canada",
|
max_length=9, choices=country_name_choices, default="Canada",
|
||||||
)
|
)
|
||||||
taxed: ormar.Boolean(choices=country_taxed_choices, default=True)
|
taxed: bool = ormar.Boolean(choices=country_taxed_choices, default=True)
|
||||||
country_code: ormar.Integer(
|
country_code: int = ormar.Integer(
|
||||||
minimum=0, maximum=1000, choices=country_country_code_choices, default=1
|
minimum=0, maximum=1000, choices=country_country_code_choices, default=1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,9 +98,9 @@ async def create_test_database():
|
|||||||
|
|
||||||
def test_model_class():
|
def test_model_class():
|
||||||
assert list(User.Meta.model_fields.keys()) == ["id", "name"]
|
assert list(User.Meta.model_fields.keys()) == ["id", "name"]
|
||||||
assert issubclass(User.Meta.model_fields["id"], pydantic.ConstrainedInt)
|
assert issubclass(User.Meta.model_fields["id"], pydantic.fields.FieldInfo)
|
||||||
assert User.Meta.model_fields["id"].primary_key is True
|
assert User.Meta.model_fields["id"].primary_key is True
|
||||||
assert issubclass(User.Meta.model_fields["name"], pydantic.ConstrainedStr)
|
assert issubclass(User.Meta.model_fields["name"], pydantic.fields.FieldInfo)
|
||||||
assert User.Meta.model_fields["name"].max_length == 100
|
assert User.Meta.model_fields["name"].max_length == 100
|
||||||
assert isinstance(User.Meta.table, sqlalchemy.Table)
|
assert isinstance(User.Meta.table, sqlalchemy.Table)
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ async def test_model_filter():
|
|||||||
assert product.pk is not None
|
assert product.pk is not None
|
||||||
assert product.name == "T-Shirt"
|
assert product.name == "T-Shirt"
|
||||||
assert product.rating == 5
|
assert product.rating == 5
|
||||||
assert product.last_delivery == datetime.now().date()
|
assert product.last_delivery == datetime.datetime.now().date()
|
||||||
|
|
||||||
products = await Product.objects.all(rating__gte=2, in_stock=True)
|
products = await Product.objects.all(rating__gte=2, in_stock=True)
|
||||||
assert len(products) == 2
|
assert len(products) == 2
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -35,8 +35,8 @@ class Category(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Item(ormar.Model):
|
class Item(ormar.Model):
|
||||||
@ -45,9 +45,9 @@ class Item(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -17,8 +18,8 @@ class Department(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True, autoincrement=False)
|
id: int = ormar.Integer(primary_key=True, autoincrement=False)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class SchoolClass(ormar.Model):
|
class SchoolClass(ormar.Model):
|
||||||
@ -27,8 +28,8 @@ class SchoolClass(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Category(ormar.Model):
|
class Category(ormar.Model):
|
||||||
@ -37,9 +38,9 @@ class Category(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
department: ormar.ForeignKey(Department, nullable=False)
|
department: Optional[Department] = ormar.ForeignKey(Department, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class Student(ormar.Model):
|
class Student(ormar.Model):
|
||||||
@ -48,10 +49,10 @@ class Student(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
schoolclass: ormar.ForeignKey(SchoolClass)
|
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class Teacher(ormar.Model):
|
class Teacher(ormar.Model):
|
||||||
@ -60,10 +61,10 @@ class Teacher(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
schoolclass: ormar.ForeignKey(SchoolClass)
|
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
|
|||||||
374
tests/test_new_annotation_style.py
Normal file
374
tests/test_new_annotation_style.py
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import pytest
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
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: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Track(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "tracks"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||||
|
title: str = ormar.String(max_length=100)
|
||||||
|
position: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class Cover(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "covers"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
album: Optional[Album] = ormar.ForeignKey(Album, related_name="cover_pictures")
|
||||||
|
title: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Organisation(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "org"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
ident: str = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"])
|
||||||
|
|
||||||
|
|
||||||
|
class Team(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "teams"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
org: Optional[Organisation] = ormar.ForeignKey(Organisation)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Member(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "members"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
team: Optional[Team] = ormar.ForeignKey(Team)
|
||||||
|
email: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
@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_wrong_query_foreign_key_type():
|
||||||
|
async with database:
|
||||||
|
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:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
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:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
album = Album(name="Jamaica")
|
||||||
|
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()
|
||||||
|
|
||||||
|
track = await Track.objects.get(title="The Bird")
|
||||||
|
assert track.album.pk == album.pk
|
||||||
|
assert isinstance(track.album, ormar.Model)
|
||||||
|
assert track.album.name is None
|
||||||
|
await track.album.load()
|
||||||
|
assert track.album.name == "Jamaica"
|
||||||
|
|
||||||
|
assert len(album.tracks) == 3
|
||||||
|
assert album.tracks[1].title == "Heart don't stand a chance"
|
||||||
|
|
||||||
|
album1 = await Album.objects.get(name="Jamaica")
|
||||||
|
assert album1.pk == album.pk
|
||||||
|
assert album1.tracks == []
|
||||||
|
|
||||||
|
await Track.objects.create(
|
||||||
|
album={"id": track.album.pk}, title="The Bird2", position=4
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_select_related():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
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_model_removal_from_relations():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
album = Album(name="Chichi")
|
||||||
|
await album.save()
|
||||||
|
track1 = Track(album=album, title="The Birdman", position=1)
|
||||||
|
track2 = Track(album=album, title="Superman", position=2)
|
||||||
|
track3 = Track(album=album, title="Wonder Woman", position=3)
|
||||||
|
await track1.save()
|
||||||
|
await track2.save()
|
||||||
|
await track3.save()
|
||||||
|
|
||||||
|
assert len(album.tracks) == 3
|
||||||
|
await album.tracks.remove(track1)
|
||||||
|
assert len(album.tracks) == 2
|
||||||
|
assert track1.album is None
|
||||||
|
|
||||||
|
await track1.update()
|
||||||
|
track1 = await Track.objects.get(title="The Birdman")
|
||||||
|
assert track1.album is None
|
||||||
|
|
||||||
|
await album.tracks.add(track1)
|
||||||
|
assert len(album.tracks) == 3
|
||||||
|
assert track1.album == album
|
||||||
|
|
||||||
|
await track1.update()
|
||||||
|
track1 = await Track.objects.select_related("album__tracks").get(
|
||||||
|
title="The Birdman"
|
||||||
|
)
|
||||||
|
album = await Album.objects.select_related("tracks").get(name="Chichi")
|
||||||
|
assert track1.album == album
|
||||||
|
|
||||||
|
track1.remove(album)
|
||||||
|
assert track1.album is None
|
||||||
|
assert len(album.tracks) == 2
|
||||||
|
|
||||||
|
track2.remove(album)
|
||||||
|
assert track2.album is None
|
||||||
|
assert len(album.tracks) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_fk_filter():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
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:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
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_wrong_choices():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
await Organisation.objects.create(ident="Test 1")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pk_filter():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
fantasies = await Album.objects.create(name="Test")
|
||||||
|
track = 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=track.pk).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:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
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:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
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():
|
||||||
|
async with database:
|
||||||
|
async with database.transaction(force_rollback=True):
|
||||||
|
with pytest.raises(RelationshipInstanceError):
|
||||||
|
org = await Organisation.objects.create(ident="ACME Ltd")
|
||||||
|
await Track.objects.create(album=org, title="Test1", position=1)
|
||||||
@ -21,8 +21,8 @@ class Model(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.String(primary_key=True, default=key, max_length=8)
|
id: str = ormar.String(primary_key=True, default=key, max_length=8)
|
||||||
name: ormar.String(max_length=32)
|
name: str = ormar.String(max_length=32)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="function")
|
@pytest.fixture(autouse=True, scope="function")
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -16,10 +18,10 @@ class Book(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
title: ormar.String(max_length=200)
|
title: str = ormar.String(max_length=200)
|
||||||
author: ormar.String(max_length=100)
|
author: str = ormar.String(max_length=100)
|
||||||
genre: ormar.String(
|
genre: str = ormar.String(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
default="Fiction",
|
default="Fiction",
|
||||||
choices=["Fiction", "Adventure", "Historic", "Fantasy"],
|
choices=["Fiction", "Adventure", "Historic", "Fantasy"],
|
||||||
@ -32,9 +34,9 @@ class ToDo(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
text: ormar.String(max_length=500)
|
text: str = ormar.String(max_length=500)
|
||||||
completed: ormar.Boolean(default=False)
|
completed: bool = ormar.Boolean(default=False)
|
||||||
|
|
||||||
|
|
||||||
class Category(ormar.Model):
|
class Category(ormar.Model):
|
||||||
@ -43,8 +45,8 @@ class Category(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=500)
|
name: str = ormar.String(max_length=500)
|
||||||
|
|
||||||
|
|
||||||
class Note(ormar.Model):
|
class Note(ormar.Model):
|
||||||
@ -53,9 +55,9 @@ class Note(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
text: ormar.String(max_length=500)
|
text: str = ormar.String(max_length=500)
|
||||||
category: ormar.ForeignKey(Category)
|
category: Optional[Category] = ormar.ForeignKey(Category)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pytest
|
import pytest
|
||||||
@ -17,8 +18,8 @@ class Department(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True, autoincrement=False)
|
id: int = ormar.Integer(primary_key=True, autoincrement=False)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class SchoolClass(ormar.Model):
|
class SchoolClass(ormar.Model):
|
||||||
@ -27,9 +28,9 @@ class SchoolClass(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
department: ormar.ForeignKey(Department, nullable=False)
|
department: Optional[Department] = ormar.ForeignKey(Department, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class Category(ormar.Model):
|
class Category(ormar.Model):
|
||||||
@ -38,8 +39,8 @@ class Category(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Student(ormar.Model):
|
class Student(ormar.Model):
|
||||||
@ -48,10 +49,10 @@ class Student(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
schoolclass: ormar.ForeignKey(SchoolClass)
|
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class Teacher(ormar.Model):
|
class Teacher(ormar.Model):
|
||||||
@ -60,10 +61,10 @@ class Teacher(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
schoolclass: ormar.ForeignKey(SchoolClass)
|
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
|
||||||
category: ormar.ForeignKey(Category, nullable=True)
|
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import databases
|
import databases
|
||||||
import pydantic
|
import pydantic
|
||||||
import pytest
|
import pytest
|
||||||
@ -16,9 +18,9 @@ class Company(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100, nullable=False)
|
name: str = ormar.String(max_length=100, nullable=False)
|
||||||
founded: ormar.Integer(nullable=True)
|
founded: int = ormar.Integer(nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class Car(ormar.Model):
|
class Car(ormar.Model):
|
||||||
@ -27,13 +29,13 @@ class Car(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
manufacturer: ormar.ForeignKey(Company)
|
manufacturer: Optional[Company] = ormar.ForeignKey(Company)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
year: ormar.Integer(nullable=True)
|
year: int = ormar.Integer(nullable=True)
|
||||||
gearbox_type: ormar.String(max_length=20, nullable=True)
|
gearbox_type: str = ormar.String(max_length=20, nullable=True)
|
||||||
gears: ormar.Integer(nullable=True)
|
gears: int = ormar.Integer(nullable=True)
|
||||||
aircon_type: ormar.String(max_length=20, nullable=True)
|
aircon_type: str = ormar.String(max_length=20, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
|||||||
@ -20,11 +20,11 @@ class Product(ormar.Model):
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
company: ormar.String(max_length=200, server_default='Acme')
|
company: str = ormar.String(max_length=200, server_default="Acme")
|
||||||
sort_order: ormar.Integer(server_default=text("10"))
|
sort_order: int = ormar.Integer(server_default=text("10"))
|
||||||
created: ormar.DateTime(server_default=func.now())
|
created: datetime = ormar.DateTime(server_default=func.now())
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
@ -44,42 +44,44 @@ async def create_test_database():
|
|||||||
|
|
||||||
|
|
||||||
def test_table_defined_properly():
|
def test_table_defined_properly():
|
||||||
assert Product.Meta.model_fields['created'].nullable
|
assert Product.Meta.model_fields["created"].nullable
|
||||||
assert not Product.__fields__['created'].required
|
assert not Product.__fields__["created"].required
|
||||||
assert Product.Meta.table.columns['created'].server_default.arg.name == 'now'
|
assert Product.Meta.table.columns["created"].server_default.arg.name == "now"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_model_creation():
|
async def test_model_creation():
|
||||||
async with database:
|
async with database:
|
||||||
async with database.transaction(force_rollback=True):
|
async with database.transaction(force_rollback=True):
|
||||||
p1 = Product(name='Test')
|
p1 = Product(name="Test")
|
||||||
assert p1.created is None
|
assert p1.created is None
|
||||||
await p1.save()
|
await p1.save()
|
||||||
await p1.load()
|
await p1.load()
|
||||||
assert p1.created is not None
|
assert p1.created is not None
|
||||||
assert p1.company == 'Acme'
|
assert p1.company == "Acme"
|
||||||
assert p1.sort_order == 10
|
assert p1.sort_order == 10
|
||||||
|
|
||||||
date = datetime.strptime('2020-10-27 11:30', '%Y-%m-%d %H:%M')
|
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)
|
p3 = await Product.objects.create(
|
||||||
|
name="Test2", created=date, company="Roadrunner", sort_order=1
|
||||||
|
)
|
||||||
assert p3.created is not None
|
assert p3.created is not None
|
||||||
assert p3.created == date
|
assert p3.created == date
|
||||||
assert p1.created != p3.created
|
assert p1.created != p3.created
|
||||||
assert p3.company == 'Roadrunner'
|
assert p3.company == "Roadrunner"
|
||||||
assert p3.sort_order == 1
|
assert p3.sort_order == 1
|
||||||
|
|
||||||
p3 = await Product.objects.get(name='Test2')
|
p3 = await Product.objects.get(name="Test2")
|
||||||
assert p3.company == 'Roadrunner'
|
assert p3.company == "Roadrunner"
|
||||||
assert p3.sort_order == 1
|
assert p3.sort_order == 1
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
p2 = await Product.objects.create(name='Test3')
|
p2 = await Product.objects.create(name="Test3")
|
||||||
assert p2.created is not None
|
assert p2.created is not None
|
||||||
assert p2.company == 'Acme'
|
assert p2.company == "Acme"
|
||||||
assert p2.sort_order == 10
|
assert p2.sort_order == 10
|
||||||
|
|
||||||
if Product.db_backend_name() != 'postgresql':
|
if Product.db_backend_name() != "postgresql":
|
||||||
# postgres use transaction timestamp so it will remain the same
|
# postgres use transaction timestamp so it will remain the same
|
||||||
assert p1.created != p2.created # pragma nocover
|
assert p1.created != p2.created # pragma nocover
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg # type: ignore
|
||||||
import databases
|
import databases
|
||||||
import pymysql
|
import pymysql
|
||||||
import pytest
|
import pytest
|
||||||
@ -21,9 +21,9 @@ class Product(ormar.Model):
|
|||||||
database = database
|
database = database
|
||||||
constraints = [ormar.UniqueColumns("name", "company")]
|
constraints = [ormar.UniqueColumns("name", "company")]
|
||||||
|
|
||||||
id: ormar.Integer(primary_key=True)
|
id: int = ormar.Integer(primary_key=True)
|
||||||
name: ormar.String(max_length=100)
|
name: str = ormar.String(max_length=100)
|
||||||
company: ormar.String(max_length=200)
|
company: str = ormar.String(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
|
|||||||
Reference in New Issue
Block a user