WIP - Pydantic v2 support (#1238)
* WIP * WIP - make test_model_definition tests pass * WIP - make test_model_methods pass * WIP - make whole test suit at least run - failing 49/443 tests * WIP fix part of the getting pydantic tests as types of fields are now kept in core schema and not on fieldsinfo * WIP fix validation in update by creating individual fields validators, failing 36/443 * WIP fix __pydantic_extra__ in intializing model, fix test related to pydantic config checks, failing 32/442 * WIP - fix enum schema in model_json_schema, failing 31/442 * WIP - fix copying through model, fix setting pydantic fields on through, fix default config and inheriting from it, failing 26/442 * WIP fix tests checking pydantic schema, fix excluding parent fields, failing 21/442 * WIP some missed files * WIP - fix validators inheritance and fix validators in generated pydantic, failing 17/442 * WIP - fix through models setting - only on reverse side of relation, but always on reverse side, failing 15/442 * WIP - fix through models setting - only on reverse side of relation, but always on reverse side, failing 15/442 * WIP - working on proper populating __dict__ for relations for new schema dumping, some work on openapi docs, failing 13/442 * WIP - remove property fields as pydantic has now computed_field on its own, failing 9/442 * WIP - fixes in docs, failing 8/442 * WIP - fix tests for largebinary schema, wrapped bytes fields fail in pydantic, will be fixed in pydantic-core, remaining is circural schema for related models, failing 6/442 * WIP - fix to pk only models in schemas * Getting test suites to pass (#1249) * wip, fixing tests * iteration, fixing some more tests * iteration, fixing some more tests * adhere to comments * adhere to comments * remove unnecessary dict call, re-add getattribute for testing * todo for reverse relationship * adhere to comments, remove prints * solve circular refs * all tests pass 🎉 * remove 3.7 from tests * add lint and type check jobs * reforat with ruff, fix jobs * rename jobs * fix imports * fix evaluate in py3.8 * partially fix coverage * fix coverage, add more tests * fix test ids * fix test ids * fix lint, fix docs, make docs fully working scripts, add test docs job * fix pyproject * pin py ver in test docs * change dir in test docs * fix pydantic warning hack * rm poetry call in test_docs * switch to pathlib in test docs * remove coverage req test docs * fix type check tests, fix part of types * fix/skip next part of types * fix next part of types * fix next part of types * fix coverage * fix coverage * fix type (bit dirty 🤷) * fix some code smells * change pre-commit * tweak workflows * remove no root from tests * switch to full python path by passing sys.executable * some small refactor in new base model, one sample test, change makefile * small refactors to reduce complexity of methods * temp add tests for prs against pydantic_v2 * remove all references to __fields__ * remove all references to construct, deprecate the method and update model_construct to be in line with pydantic * deprecate dict and add model_dump, todo switch to model_dict in calls * fix tests * change to union * change to union * change to model_dump and model_dump_json from dict and json deprecated methods, deprecate them in ormar too * finish switching dict() -> model_dump() * finish switching json() -> model_dump_json() * remove fully pydantic_only * switch to extra for payment card, change missed json calls * fix coverage - no more warnings internal * fix coverage - no more warnings internal - part 2 * split model_construct into own and pydantic parts * split determine pydantic field type * change to new field validators * fix benchmarks, add codspeed instead of pytest-benchmark, add action and gh workflow * restore pytest-benchmark * remove codspeed * pin pydantic version, restore codspeed * change on push to pydantic_v2 to trigger first one * Use lifespan function instead of event (#1259) * check return types * fix imports order, set warnings=False on json that passes the dict, fix unnecessary loop in one of the test * remove references to model's meta as it's now ormar config, rename related methods too * filter out pydantic serializer warnings * remove choices leftovers * remove leftovers after property_fields, keep only enough to exclude them in initialization * add migration guide * fix meta references * downgrade databases for now * Change line numbers in documentation (#1265) * proofread and fix the docs, part 1 * proofread and fix the docs for models * proofread and fix the docs for fields * proofread and fix the docs for relations * proofread and fix rest of the docs, add release notes for 0.20 * create tables in new docs src * cleanup old deps, uncomment docs publish on tag * fix import reorder --------- Co-authored-by: TouwaStar <30479449+TouwaStar@users.noreply.github.com> Co-authored-by: Goran Mekić <meka@tilda.center>
This commit is contained in:
6
.flake8
6
.flake8
@ -1,6 +0,0 @@
|
||||
[flake8]
|
||||
ignore = ANN101, ANN102, W503, S101, CFQ004, S311
|
||||
max-complexity = 8
|
||||
max-line-length = 88
|
||||
import-order-style = pycharm
|
||||
exclude = p38venv,.pytest_cache
|
||||
6
.github/workflows/deploy-docs.yml
vendored
6
.github/workflows/deploy-docs.yml
vendored
@ -27,6 +27,6 @@ jobs:
|
||||
run: |
|
||||
echo $RELEASE_VERSION
|
||||
echo ${{ env.RELEASE_VERSION }}
|
||||
# - name: Deploy
|
||||
# run: |
|
||||
# mike deploy --push --update-aliases ${{ env.RELEASE_VERSION }} latest
|
||||
- name: Deploy
|
||||
run: |
|
||||
mike deploy --push --update-aliases ${{ env.RELEASE_VERSION }} latest
|
||||
|
||||
44
.github/workflows/lint.yml
vendored
Normal file
44
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
branches: [ master, pydantic_v2 ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: "Python ${{ matrix.python-version }}"
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/ormar'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1.3.3
|
||||
with:
|
||||
version: 1.4.2
|
||||
virtualenvs-create: false
|
||||
|
||||
- name: Poetry details
|
||||
run: |
|
||||
poetry --version
|
||||
poetry config --list
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install --extras "all" --no-root
|
||||
|
||||
- name: Format
|
||||
run: make fmt
|
||||
|
||||
- name: Lint
|
||||
run: make lint
|
||||
62
.github/workflows/test-package.yml
vendored
62
.github/workflows/test-package.yml
vendored
@ -1,14 +1,14 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: build
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ master, pydantic_v2 ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/ormar'
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7, 3.8, 3.9, "3.10", 3.11]
|
||||
python-version: [3.8, 3.9, "3.10", 3.11]
|
||||
fail-fast: false
|
||||
services:
|
||||
mysql:
|
||||
@ -39,35 +39,75 @@ jobs:
|
||||
POSTGRES_DB: testsuite
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 --name postgres
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1.3.3
|
||||
with:
|
||||
version: 1.4.2
|
||||
virtualenvs-create: false
|
||||
|
||||
- name: Poetry details
|
||||
run: |
|
||||
python -m pip install poetry==1.4.2
|
||||
poetry install --extras "all"
|
||||
env:
|
||||
POETRY_VIRTUALENVS_CREATE: false
|
||||
poetry --version
|
||||
poetry config --list
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install --extras "all"
|
||||
|
||||
- name: Run mysql
|
||||
env:
|
||||
DATABASE_URL: "mysql://username:password@127.0.0.1:3306/testsuite"
|
||||
run: bash scripts/test.sh
|
||||
|
||||
- name: Install postgresql-client
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes postgresql-client
|
||||
|
||||
- name: Connect to PostgreSQL with CLI
|
||||
run: env PGPASSWORD=password psql -h localhost -U username -c 'SELECT VERSION();' testsuite
|
||||
|
||||
- name: Show max connections
|
||||
run: env PGPASSWORD=password psql -h localhost -U username -c 'SHOW max_connections;' testsuite
|
||||
|
||||
- name: Alter max connections
|
||||
run: |
|
||||
|
||||
docker exec -i postgres bash << EOF
|
||||
sed -i -e 's/max_connections = 100/max_connections = 1000/' /var/lib/postgresql/data/postgresql.conf
|
||||
sed -i -e 's/shared_buffers = 128MB/shared_buffers = 512MB/' /var/lib/postgresql/data/postgresql.conf
|
||||
EOF
|
||||
docker restart --time 0 postgres
|
||||
sleep 5
|
||||
|
||||
- name: Show max connections
|
||||
run: env PGPASSWORD=password psql -h localhost -U username -c 'SHOW max_connections;' testsuite
|
||||
|
||||
- name: Run postgres
|
||||
env:
|
||||
DATABASE_URL: "postgresql://username:password@localhost:5432/testsuite"
|
||||
run: bash scripts/test.sh
|
||||
|
||||
- name: Run sqlite
|
||||
env:
|
||||
DATABASE_URL: "sqlite:///testsuite"
|
||||
run: bash scripts/test.sh
|
||||
- run: mypy ormar tests benchmarks
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3.1.6
|
||||
|
||||
- name: Test & publish code coverage
|
||||
uses: paambaati/codeclimate-action@v5.0.0
|
||||
if: github.event.pull_request.head.repo.full_name == 'collerek/ormar'
|
||||
|
||||
31
.github/workflows/test_docs.yml
vendored
Normal file
31
.github/workflows/test_docs.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: test_docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
branches: [ master, pydantic_v2 ]
|
||||
|
||||
jobs:
|
||||
tests_docs:
|
||||
name: "Python ${{ matrix.python-version }}"
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/ormar'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install poetry==1.4.2
|
||||
poetry install --extras "all"
|
||||
env:
|
||||
POETRY_VIRTUALENVS_CREATE: false
|
||||
- name: Test docs
|
||||
run: bash scripts/test_docs.sh
|
||||
41
.github/workflows/type-check.yml
vendored
Normal file
41
.github/workflows/type-check.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: type_check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
branches: [ master, pydantic_v2 ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: "Python ${{ matrix.python-version }}"
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/ormar'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1.3.3
|
||||
with:
|
||||
version: 1.4.2
|
||||
virtualenvs-create: false
|
||||
|
||||
- name: Poetry details
|
||||
run: |
|
||||
poetry --version
|
||||
poetry config --list
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install --extras "all" --no-root
|
||||
|
||||
- name: Type check
|
||||
run: make type_check
|
||||
@ -1,31 +1,9 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: ^(docs_src/|examples/)
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
exclude: ^(docs_src/|examples/|tests/)
|
||||
args: [ '--max-line-length=88' ]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.982
|
||||
hooks:
|
||||
- id: mypy
|
||||
exclude: ^(docs_src/|examples/)
|
||||
args: [--no-strict-optional, --ignore-missing-imports]
|
||||
additional_dependencies: [
|
||||
types-ujson>=0.1.1,
|
||||
types-PyMySQL>=1.0.2,
|
||||
types-ipaddress>=1.0.0,
|
||||
types-enum34>=1.1.0,
|
||||
types-cryptography>=3.3.5,
|
||||
types-orjson>=3.6.0,
|
||||
types-aiofiles>=0.1.9,
|
||||
types-pkg-resources>=0.1.3,
|
||||
types-requests>=2.25.9,
|
||||
types-toml>=0.10.0,
|
||||
pydantic>=1.8.2
|
||||
]
|
||||
- id: pre-commit-local
|
||||
name: format
|
||||
entry: make pre-commit
|
||||
language: python
|
||||
pass_filenames: false
|
||||
|
||||
|
||||
20
Makefile
20
Makefile
@ -15,18 +15,22 @@ test_mysql:
|
||||
test_sqlite:
|
||||
bash scripts/test.sh -svv
|
||||
|
||||
test_docs:
|
||||
bash scripts/test_docs.sh -svv
|
||||
|
||||
test:
|
||||
pytest
|
||||
pytest -svv tests/
|
||||
|
||||
coverage:
|
||||
pytest --cov=ormar --cov=tests --cov-fail-under=100 --cov-report=term-missing
|
||||
pytest --cov=ormar --cov=tests --cov-fail-under=100 --cov-report=term-missing tests
|
||||
|
||||
black:
|
||||
black ormar tests
|
||||
type_check:
|
||||
mkdir -p .mypy_cache && poetry run python -m mypy ormar tests --ignore-missing-imports --install-types --non-interactive
|
||||
|
||||
lint:
|
||||
black ormar tests
|
||||
flake8 ormar
|
||||
poetry run python -m ruff . --fix
|
||||
|
||||
mypy:
|
||||
mypy ormar tests
|
||||
fmt:
|
||||
poetry run python -m black .
|
||||
|
||||
pre-commit: fmt lint type_check
|
||||
35
README.md
35
README.md
@ -171,39 +171,31 @@ import ormar
|
||||
import sqlalchemy
|
||||
|
||||
DATABASE_URL = "sqlite:///db.sqlite"
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
# note that this step is optional -> all ormar cares is a internal
|
||||
# class with name Meta and proper parameters, but this way you do not
|
||||
# have to repeat the same parameters if you use only one database
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
engine=sqlalchemy.create_engine(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
# Note that all type hints are optional
|
||||
# below is a perfectly valid model declaration
|
||||
# class Author(ormar.Model):
|
||||
# class Meta(BaseMeta):
|
||||
# tablename = "authors"
|
||||
# ormar_config = base_ormar_config.copy(tablename="authors")
|
||||
#
|
||||
# id = ormar.Integer(primary_key=True) # <= notice no field types
|
||||
# name = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy(tablename="authors")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
ormar_config = base_ormar_config.copy(tablename="books")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
@ -214,10 +206,9 @@ class Book(ormar.Model):
|
||||
# create the database
|
||||
# note that in production you should use migrations
|
||||
# note that this is not required if you connect to existing database
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
# just to be sure we clear the db before
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
base_ormar_config.metadata.drop_all(base_ormar_config.engine)
|
||||
base_ormar_config.metadata.create_all(base_ormar_config.engine)
|
||||
|
||||
|
||||
# all functions below are divided into functionality categories
|
||||
@ -546,7 +537,7 @@ async def raw_data():
|
||||
async def with_connect(function):
|
||||
# note that for any other backend than sqlite you actually need to
|
||||
# connect to the database to perform db operations
|
||||
async with database:
|
||||
async with base_ormar_config.database:
|
||||
await function()
|
||||
|
||||
# note that if you use framework like `fastapi` you shouldn't connect
|
||||
@ -576,7 +567,7 @@ for func in [
|
||||
asyncio.run(with_connect(func))
|
||||
|
||||
# drop the database tables
|
||||
metadata.drop_all(engine)
|
||||
base_ormar_config.metadata.drop_all(base_ormar_config.engine)
|
||||
```
|
||||
|
||||
## Ormar Specification
|
||||
@ -654,7 +645,6 @@ The following keyword arguments are supported on all field types.
|
||||
* `unique: bool`
|
||||
* `choices: typing.Sequence`
|
||||
* `name: str`
|
||||
* `pydantic_only: bool`
|
||||
|
||||
All fields are required unless one of the following is set:
|
||||
|
||||
@ -664,7 +654,6 @@ All fields are required unless one of the following is set:
|
||||
* `server_default` - Set a default value for the field on server side (like sqlalchemy's `func.now()`). **Not available for relation fields**
|
||||
* `primary key` with `autoincrement` - When a column is set to primary key and autoincrement is set on this column.
|
||||
Autoincrement is set by default on int primary keys.
|
||||
* `pydantic_only` - Field is available only as normal pydantic field, not stored in the database.
|
||||
|
||||
### Available signals
|
||||
|
||||
|
||||
@ -3,31 +3,20 @@ import random
|
||||
import string
|
||||
import time
|
||||
|
||||
import databases
|
||||
import nest_asyncio
|
||||
import ormar
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
from tests.lifespan import init_tests
|
||||
from tests.settings import create_config
|
||||
|
||||
base_ormar_config = create_config()
|
||||
nest_asyncio.apply()
|
||||
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy(tablename="authors")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -41,8 +30,7 @@ class AuthorWithManyFields(Author):
|
||||
|
||||
|
||||
class Publisher(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "publishers"
|
||||
ormar_config = base_ormar_config.copy(tablename="publishers")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -50,8 +38,7 @@ class Publisher(ormar.Model):
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
ormar_config = base_ormar_config.copy(tablename="books")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Author = ormar.ForeignKey(Author, index=True)
|
||||
@ -60,13 +47,7 @@ class Book(ormar.Model):
|
||||
year: int = ormar.Integer(nullable=True)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="function") # TODO: fix this to be module
|
||||
def create_test_database():
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
yield
|
||||
metadata.drop_all(engine)
|
||||
create_test_database = init_tests(base_ormar_config, scope="function")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
@ -86,7 +67,7 @@ async def authors_in_db(num_models: int):
|
||||
authors = [
|
||||
Author(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
for i in range(0, num_models)
|
||||
]
|
||||
|
||||
@ -15,7 +15,7 @@ async def test_making_and_inserting_models_in_bulk(aio_benchmark, num_models: in
|
||||
authors = [
|
||||
Author(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
for i in range(0, num_models)
|
||||
]
|
||||
|
||||
@ -16,7 +16,7 @@ async def test_creating_models_individually(aio_benchmark, num_models: int):
|
||||
for idx in range(0, num_models):
|
||||
author = await Author.objects.create(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
authors.append(author)
|
||||
return authors
|
||||
@ -62,7 +62,7 @@ async def test_get_or_create_when_create(aio_benchmark, num_models: int):
|
||||
for idx in range(0, num_models):
|
||||
author, created = await Author.objects.get_or_create(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
assert created
|
||||
authors.append(author)
|
||||
@ -81,7 +81,7 @@ async def test_update_or_create_when_create(aio_benchmark, num_models: int):
|
||||
for idx in range(0, num_models):
|
||||
author = await Author.objects.update_or_create(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
authors.append(author)
|
||||
return authors
|
||||
|
||||
@ -15,13 +15,13 @@ async def test_initializing_models(aio_benchmark, num_models: int):
|
||||
authors = [
|
||||
Author(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
for i in range(0, num_models)
|
||||
]
|
||||
assert len(authors) == num_models
|
||||
|
||||
initialize_models(num_models)
|
||||
_ = initialize_models(num_models)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_models", [10, 20, 40])
|
||||
|
||||
@ -15,7 +15,7 @@ async def test_saving_models_individually(aio_benchmark, num_models: int):
|
||||
authors = [
|
||||
Author(
|
||||
name="".join(random.sample(string.ascii_letters, 5)),
|
||||
score=random.random() * 100,
|
||||
score=int(random.random() * 100),
|
||||
)
|
||||
for i in range(0, num_models)
|
||||
]
|
||||
|
||||
@ -16,17 +16,18 @@ Here you can find a very simple sample application code.
|
||||
It's divided into subsections for clarity.
|
||||
|
||||
!!!note
|
||||
If you want to read more on how you can use ormar models in fastapi requests and
|
||||
responses check the [responses](response.md) and [requests](requests.md) documentation.
|
||||
If you want to read more on how you can use ormar models in fastapi requests and
|
||||
responses check the [responses](response.md) and [requests](requests.md) documentation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
!!!note
|
||||
Note that you can find the full quick start script in the [github](https://github.com/collerek/ormar) repo under examples.
|
||||
Note that you can find the full quick start script in the [github](https://github.com/collerek/ormar) repo under examples.
|
||||
|
||||
### Imports and initialization
|
||||
|
||||
First take care of the imports and initialization
|
||||
Define startup and shutdown procedures using FastAPI lifespan and use is in the
|
||||
application.
|
||||
```python
|
||||
from typing import List, Optional
|
||||
|
||||
@ -36,29 +37,26 @@ from fastapi import FastAPI
|
||||
|
||||
import ormar
|
||||
|
||||
app = FastAPI()
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database("sqlite:///test.db")
|
||||
app.state.database = database
|
||||
```
|
||||
|
||||
### Database connection
|
||||
|
||||
Next define startup and shutdown events (or use middleware)
|
||||
- note that this is `databases` specific setting not the ormar one
|
||||
```python
|
||||
@app.on_event("startup")
|
||||
async def startup() -> None:
|
||||
database_ = app.state.database
|
||||
if not database_.is_connected:
|
||||
await database_.connect()
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown() -> None:
|
||||
database_ = app.state.database
|
||||
if database_.is_connected:
|
||||
await database_.disconnect()
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
if not config.database.is_connected:
|
||||
await config.database.connect()
|
||||
|
||||
yield
|
||||
|
||||
if config.database.is_connected:
|
||||
await config.database.disconnect()
|
||||
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database("sqlite:///test.db"),
|
||||
)
|
||||
app = FastAPI(lifespan=lifespan(base_ormar_config))
|
||||
```
|
||||
|
||||
!!!info
|
||||
@ -71,21 +69,21 @@ Define ormar models with appropriate fields.
|
||||
Those models will be used instead of pydantic ones.
|
||||
|
||||
```python
|
||||
base_ormar_config = OrmarConfig(
|
||||
metadata = metadata
|
||||
database = database
|
||||
)
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -122,7 +120,7 @@ async def create_category(category: Category):
|
||||
@app.put("/items/{item_id}")
|
||||
async def get_item(item_id: int, item: Item):
|
||||
item_db = await Item.objects.get(pk=item_id)
|
||||
return await item_db.update(**item.dict())
|
||||
return await item_db.update(**item.model_dump())
|
||||
|
||||
|
||||
@app.delete("/items/{item_id}")
|
||||
@ -197,14 +195,14 @@ def test_all_endpoints():
|
||||
assert items[0] == item
|
||||
|
||||
item.name = "New name"
|
||||
response = client.put(f"/items/{item.pk}", json=item.dict())
|
||||
assert response.json() == item.dict()
|
||||
response = client.put(f"/items/{item.pk}", json=item.model_dump())
|
||||
assert response.json() == item.model_dump()
|
||||
|
||||
response = client.get("/items/")
|
||||
items = [Item(**item) for item in response.json()]
|
||||
assert items[0].name == "New name"
|
||||
|
||||
response = client.delete(f"/items/{item.pk}", json=item.dict())
|
||||
response = client.delete(f"/items/{item.pk}", json=item.model_dump())
|
||||
assert response.json().get("deleted_rows", "__UNDEFINED__") != "__UNDEFINED__"
|
||||
response = client.get("/items/")
|
||||
items = response.json()
|
||||
|
||||
@ -23,11 +23,13 @@ Field is not required if (any/many/all) of following:
|
||||
|
||||
Example:
|
||||
```python
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata
|
||||
database=database
|
||||
)
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename: str = "users"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
email: str = ormar.String(max_length=255)
|
||||
@ -42,8 +44,8 @@ In above example fields `id` (is an `autoincrement` `Integer`), `first_name` ( h
|
||||
If the field is nullable you don't have to include it in payload during creation as well as in response, so given example above you can:
|
||||
|
||||
!!!Warning
|
||||
Note that although you do not have to pass the optional field, you still **can** do it.
|
||||
And if someone will pass a value it will be used later unless you take measures to prevent it.
|
||||
Note that although you do not have to pass the optional field, you still **can** do it.
|
||||
And if someone will pass a value it will be used later unless you take measures to prevent it.
|
||||
|
||||
```python
|
||||
# note that app is an FastApi app
|
||||
@ -66,18 +68,18 @@ RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority
|
||||
@app.post("/users3/", response_model=User) # here you can also use both ormar/pydantic
|
||||
async def create_user3(user: RequestUser): # use the generated model here
|
||||
# note how now user is pydantic and not ormar Model so you need to convert
|
||||
return await User(**user.dict()).save()
|
||||
return await User(**user.model_dump()).save()
|
||||
```
|
||||
|
||||
!!!Note
|
||||
To see more examples and read more visit [get_pydantic](../models/methods.md#get_pydantic) part of the documentation.
|
||||
To see more examples and read more visit [get_pydantic](../models/methods.md#get_pydantic) part of the documentation.
|
||||
|
||||
!!!Warning
|
||||
The `get_pydantic` method generates all models in a tree of nested models according to an algorithm that allows to avoid loops in models (same algorithm that is used in `dict()`, `select_all()` etc.)
|
||||
The `get_pydantic` method generates all models in a tree of nested models according to an algorithm that allows to avoid loops in models (same algorithm that is used in `model_dump()`, `select_all()` etc.)
|
||||
|
||||
That means that nested models won't have reference to parent model (by default ormar relation is bidirectional).
|
||||
That means that nested models won't have reference to parent model (by default ormar relation is bidirectional).
|
||||
|
||||
Note also that if given model exists in a tree more than once it will be doubled in pydantic models (each occurrence will have separate own model). That way you can exclude/include different fields on different leafs of the tree.
|
||||
Note also that if given model exists in a tree more than once it will be doubled in pydantic models (each occurrence will have separate own model). That way you can exclude/include different fields on different leafs of the tree.
|
||||
|
||||
#### Mypy and type checking
|
||||
|
||||
@ -94,7 +96,7 @@ RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority
|
||||
@app.post("/users3/", response_model=User)
|
||||
async def create_user3(user: RequestUser): # type: ignore
|
||||
# note how now user is not ormar Model so you need to convert
|
||||
return await User(**user.dict()).save()
|
||||
return await User(**user.model_dump()).save()
|
||||
```
|
||||
|
||||
The second one is a little bit more hacky and utilizes a way in which fastapi extract function parameters.
|
||||
@ -105,7 +107,7 @@ You can overwrite the `__annotations__` entry for given param.
|
||||
RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
|
||||
# do not use the app decorator
|
||||
async def create_user3(user: User): # use ormar model here
|
||||
return await User(**user.dict()).save()
|
||||
return await User(**user.model_dump()).save()
|
||||
# overwrite the function annotations entry for user param with generated model
|
||||
create_user3.__annotations__["user"] = RequestUser
|
||||
# manually call app functions (app.get, app.post etc.) and pass your function reference
|
||||
@ -126,8 +128,7 @@ Sample:
|
||||
import pydantic
|
||||
|
||||
class UserCreate(pydantic.BaseModel):
|
||||
class Config:
|
||||
orm_mode = True
|
||||
model_config = pydantic.ConfigDict(from_attributes=True)
|
||||
|
||||
email: str
|
||||
first_name: str
|
||||
@ -139,5 +140,5 @@ class UserCreate(pydantic.BaseModel):
|
||||
async def create_user3(user: UserCreate): # use pydantic model here
|
||||
# note how now request param is a pydantic model and not the ormar one
|
||||
# so you need to parse/convert it to ormar before you can use database
|
||||
return await User(**user.dict()).save()
|
||||
return await User(**user.model_dump()).save()
|
||||
```
|
||||
|
||||
@ -22,11 +22,13 @@ Field is not required if (any/many/all) of following:
|
||||
|
||||
Example:
|
||||
```python
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata
|
||||
database=database
|
||||
)
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename: str = "users"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
email: str = ormar.String(max_length=255)
|
||||
@ -50,9 +52,9 @@ async def create_user(user: User): # here we use ormar.Model in request paramet
|
||||
That means that if you do not pass i.e. `first_name` in request it will validate correctly (as field is optional), save in the database and return the saved record without this field (which will also pass validation).
|
||||
|
||||
!!!Note
|
||||
Note that although you do not pass the **field value**, the **field itself** is still present in the `response_model` that means it **will be present in response data** and set to `None`.
|
||||
Note that although you do not pass the **field value**, the **field itself** is still present in the `response_model` that means it **will be present in response data** and set to `None`.
|
||||
|
||||
If you want to fully exclude the field from the result read on.
|
||||
If you want to fully exclude the field from the result read on.
|
||||
|
||||
### FastApi `response_model_exclude`
|
||||
|
||||
@ -61,7 +63,7 @@ Fastapi has `response_model_exclude` that accepts a set (or a list) of field nam
|
||||
That has it's limitation as `ormar` and `pydantic` accepts also dictionaries in which you can set exclude/include columns also on nested models (more on this below)
|
||||
|
||||
!!!Warning
|
||||
Note that you cannot exclude required fields when using `response_model` as it will fail during validation.
|
||||
Note that you cannot exclude required fields when using `response_model` as it will fail during validation.
|
||||
|
||||
```python
|
||||
@app.post("/users/", response_model=User, response_model_exclude={"password"})
|
||||
@ -96,9 +98,9 @@ with client as client:
|
||||
```
|
||||
|
||||
!!!Note
|
||||
Note how in above example `password` field is fully gone from the response data.
|
||||
Note how in above example `password` field is fully gone from the response data.
|
||||
|
||||
Note that you can use this method only for non-required fields.
|
||||
Note that you can use this method only for non-required fields.
|
||||
|
||||
#### Nested models excludes
|
||||
|
||||
@ -111,13 +113,13 @@ One is a dictionary with nested fields that represents the model tree structure,
|
||||
Assume for a second that our user's category is a separate model:
|
||||
|
||||
```python
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata
|
||||
database=database
|
||||
)
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename: str = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=255)
|
||||
@ -125,8 +127,7 @@ class Category(ormar.Model):
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename: str = "users"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
email: str = ormar.String(max_length=255)
|
||||
@ -147,39 +148,39 @@ Note that you can go in deeper models with double underscore, and if you want to
|
||||
In example `response_model_exclude={"category__priority", "category__other_field", category__nested_model__nested_model_field}` etc.
|
||||
|
||||
!!!Note
|
||||
To read more about possible excludes and how to structure your exclude dictionary or set visit [fields](../queries/select-columns.md#fields) section of documentation
|
||||
To read more about possible excludes and how to structure your exclude dictionary or set visit [fields](../queries/select-columns.md#fields) section of documentation
|
||||
|
||||
!!!Note
|
||||
Note that apart from `response_model_exclude` parameter `fastapi` supports also other parameters inherited from `pydantic`.
|
||||
All of them works also with ormar, but can have some nuances so best to read [dict](../models/methods.md#dict) part of the documentation.
|
||||
Note that apart from `response_model_exclude` parameter `fastapi` supports also other parameters inherited from `pydantic`.
|
||||
All of them works also with ormar, but can have some nuances so best to read [dict](../models/methods.md#dict) part of the documentation.
|
||||
|
||||
### Exclude in `Model.dict()`
|
||||
### Exclude in `Model.model_dump()`
|
||||
|
||||
Alternatively you can just return a dict from `ormar.Model` and use .
|
||||
|
||||
Like this you can also set exclude/include as dict and exclude fields on nested models too.
|
||||
|
||||
!!!Warning
|
||||
Not using a `response_model` will cause api documentation having no response example and schema since in theory response can have any format.
|
||||
Not using a `response_model` will cause api documentation having no response example and schema since in theory response can have any format.
|
||||
|
||||
```python
|
||||
@app.post("/users2/", response_model=User)
|
||||
async def create_user2(user: User):
|
||||
user = await user.save()
|
||||
return user.dict(exclude={'password'})
|
||||
# could be also something like return user.dict(exclude={'category': {'priority'}}) to exclude category priority
|
||||
return user.model_dump(exclude={'password'})
|
||||
# could be also something like return user.model_dump(exclude={'category': {'priority'}}) to exclude category priority
|
||||
```
|
||||
|
||||
!!!Note
|
||||
Note that above example will nullify the password field even if you pass it in request, but the **field will be still there** as it's part of the response schema, the value will be set to `None`.
|
||||
Note that above example will nullify the password field even if you pass it in request, but the **field will be still there** as it's part of the response schema, the value will be set to `None`.
|
||||
|
||||
If you want to fully exclude the field with this approach simply don't use `response_model` and exclude in Model's dict()
|
||||
If you want to fully exclude the field with this approach simply don't use `response_model` and exclude in Model's model_dump()
|
||||
|
||||
Alternatively you can just return a dict from ormar model.
|
||||
Like this you can also set exclude/include as dict and exclude fields on nested models.
|
||||
|
||||
!!!Note
|
||||
In theory you loose validation of response here but since you operate on `ormar.Models` the response data have already been validated after db query (as ormar model is pydantic model).
|
||||
In theory you loose validation of response here but since you operate on `ormar.Models` the response data have already been validated after db query (as ormar model is pydantic model).
|
||||
|
||||
So if you skip `response_model` altogether you can do something like this:
|
||||
|
||||
@ -187,13 +188,13 @@ So if you skip `response_model` altogether you can do something like this:
|
||||
@app.post("/users4/") # note no response_model
|
||||
async def create_user4(user: User):
|
||||
user = await user.save()
|
||||
return user.dict(exclude={'last_name'})
|
||||
return user.model_dump(exclude={'last_name'})
|
||||
```
|
||||
|
||||
!!!Note
|
||||
Note that when you skip the response_model you can now **exclude also required fields** as the response is no longer validated after being returned.
|
||||
Note that when you skip the response_model you can now **exclude also required fields** as the response is no longer validated after being returned.
|
||||
|
||||
The cost of this solution is that you loose also api documentation as response schema in unknown from fastapi perspective.
|
||||
The cost of this solution is that you loose also api documentation as response schema in unknown from fastapi perspective.
|
||||
|
||||
### Generate `pydantic` model from `ormar.Model`
|
||||
|
||||
@ -210,14 +211,14 @@ async def create_user3(user: User):
|
||||
```
|
||||
|
||||
!!!Note
|
||||
To see more examples and read more visit [get_pydantic](../models/methods.md#get_pydantic) part of the documentation.
|
||||
To see more examples and read more visit [get_pydantic](../models/methods.md#get_pydantic) part of the documentation.
|
||||
|
||||
!!!Warning
|
||||
The `get_pydantic` method generates all models in a tree of nested models according to an algorithm that allows to avoid loops in models (same algorithm that is used in `dict()`, `select_all()` etc.)
|
||||
The `get_pydantic` method generates all models in a tree of nested models according to an algorithm that allows to avoid loops in models (same algorithm that is used in `model_dump()`, `select_all()` etc.)
|
||||
|
||||
That means that nested models won't have reference to parent model (by default ormar relation is bidirectional).
|
||||
That means that nested models won't have reference to parent model (by default ormar relation is bidirectional).
|
||||
|
||||
Note also that if given model exists in a tree more than once it will be doubled in pydantic models (each occurrence will have separate own model). That way you can exclude/include different fields on different leafs of the tree.
|
||||
Note also that if given model exists in a tree more than once it will be doubled in pydantic models (each occurrence will have separate own model). That way you can exclude/include different fields on different leafs of the tree.
|
||||
|
||||
### Separate `pydantic` model
|
||||
|
||||
@ -229,8 +230,7 @@ Sample:
|
||||
import pydantic
|
||||
|
||||
class UserBase(pydantic.BaseModel):
|
||||
class Config:
|
||||
orm_mode = True
|
||||
model_config = pydantic.ConfigDict(from_attributes=True)
|
||||
|
||||
email: str
|
||||
first_name: str
|
||||
|
||||
@ -29,7 +29,6 @@ Automatically changed to True if user provide one of the following:
|
||||
* `default` value or function is provided
|
||||
* `server_default` value or function is provided
|
||||
* `autoincrement` is set on `Integer` `primary_key` field
|
||||
* **[DEPRECATED]**`pydantic_only=True` is set
|
||||
|
||||
Specifies if field is optional or required, used both with sql and pydantic.
|
||||
|
||||
@ -109,7 +108,7 @@ Used in sql only.
|
||||
|
||||
Sample usage:
|
||||
|
||||
```Python hl_lines="21-23"
|
||||
```Python hl_lines="20-22"
|
||||
--8<-- "../docs_src/fields/docs004.py"
|
||||
```
|
||||
|
||||
@ -167,20 +166,6 @@ Sets the unique constraint on a table's column.
|
||||
|
||||
Used in sql only.
|
||||
|
||||
## pydantic_only (**DEPRECATED**)
|
||||
|
||||
**This parameter is deprecated and will be removed in one of next releases!**
|
||||
|
||||
**To check how to declare pydantic only fields that are not saved into database see [pydantic fields section](pydantic-fields.md)**
|
||||
|
||||
`pydantic_only`: `bool` = `False`
|
||||
|
||||
Prevents creation of a sql column for given field.
|
||||
|
||||
Used for data related to given model but not to be stored in the database.
|
||||
|
||||
Used in pydantic only.
|
||||
|
||||
## overwrite_pydantic_type
|
||||
|
||||
By default, ormar uses predefined pydantic field types that it applies on model creation (hence the type hints are optional).
|
||||
@ -189,22 +174,25 @@ If you want to, you can apply your own type, that will be **completely** replaci
|
||||
So it's on you as a user to provide a type that is valid in the context of given ormar field type.
|
||||
|
||||
!!!warning
|
||||
Note that by default you should use build in arguments that are passed to underlying pydantic field.
|
||||
Note that by default you should use build in arguments that are passed to underlying pydantic field.
|
||||
|
||||
You can check what arguments are supported in field types section or in [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation) docs.
|
||||
You can check what arguments are supported in field types section or in [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation) docs.
|
||||
|
||||
!!!danger
|
||||
Setting a wrong type of pydantic field can break your model, so overwrite it only when you know what you are doing.
|
||||
Setting a wrong type of pydantic field can break your model, so overwrite it only when you know what you are doing.
|
||||
|
||||
As it's easy to break functionality of ormar the `overwrite_pydantic_type` argument is not available on relation fields!
|
||||
As it's easy to break functionality of ormar the `overwrite_pydantic_type` argument is not available on relation fields!
|
||||
|
||||
```python
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata
|
||||
database=database
|
||||
)
|
||||
|
||||
|
||||
# sample overwrites
|
||||
class OverwriteTest(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "overwrites"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="overwrites")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
my_int: str = ormar.Integer(overwrite_pydantic_type=PositiveInt)
|
||||
@ -212,18 +200,6 @@ class OverwriteTest(ormar.Model):
|
||||
overwrite_pydantic_type=Optional[Json[Dict[str, int]]])
|
||||
```
|
||||
|
||||
## choices
|
||||
|
||||
`choices`: `Sequence` = `[]`
|
||||
|
||||
A set of choices allowed to be used for given field.
|
||||
|
||||
Used for data validation on pydantic side.
|
||||
|
||||
Prevents insertion of value not present in the choices list.
|
||||
|
||||
Used in pydantic only.
|
||||
|
||||
[relations]: ../relations/index.md
|
||||
[queries]: ../queries/index.md
|
||||
[pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types
|
||||
|
||||
@ -17,10 +17,14 @@ well as both-way encryption/decryption (`FERNET` backend).
|
||||
|
||||
To encrypt a field you need to pass at minimum `encrypt_secret` and `encrypt_backend` parameters.
|
||||
|
||||
```python hl_lines="7-8"
|
||||
```python hl_lines="10-12"
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata
|
||||
database=database
|
||||
)
|
||||
|
||||
class Filter(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "filters"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100,
|
||||
@ -59,8 +63,7 @@ Note that since this backend never decrypt the stored value it's only applicable
|
||||
|
||||
```python
|
||||
class Hash(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "hashes"
|
||||
ormar_config = base_ormar_config.copy(tablename="hashes")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=128,
|
||||
@ -106,8 +109,7 @@ as the returned value is parsed to corresponding python type.
|
||||
|
||||
```python
|
||||
class Filter(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "filters"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100,
|
||||
@ -152,8 +154,7 @@ argument by `encrypt_custom_backend`.
|
||||
|
||||
```python
|
||||
class Filter(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "filters"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
# Fields
|
||||
|
||||
|
||||
There are 12 basic model field types and a special `ForeignKey` and `Many2Many` fields to establish relationships between models.
|
||||
There are 12 basic model field types and a special `ForeignKey` and `ManyToMany` fields to establish relationships between models.
|
||||
|
||||
!!!tip
|
||||
For explanation of `ForeignKey` and `Many2Many` fields check [relations][relations].
|
||||
For explanation of `ForeignKey` and `ManyToMany` fields check [relations][relations].
|
||||
|
||||
|
||||
Each of the `Fields` has assigned both `sqlalchemy` column class and python type that is used to create `pydantic` model.
|
||||
@ -160,11 +160,16 @@ That way you can i.e. set the value by API, even if value is not `utf-8` compati
|
||||
```python
|
||||
import base64
|
||||
... # other imports skipped for brevity
|
||||
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata
|
||||
database=database
|
||||
)
|
||||
|
||||
|
||||
class LargeBinaryStr(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "my_str_blobs"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="my_str_blobs")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
test_binary: str = ormar.LargeBinary(
|
||||
@ -215,46 +220,6 @@ So which one to use depends on the backend you use and on the column/ data type
|
||||
* Sqlalchemy column: `sqlalchemy.Enum`
|
||||
* Type (used for pydantic): `Type[Enum]`
|
||||
|
||||
#### Choices
|
||||
You can change any field into `Enum` like field by passing a `choices` list that is accepted by all Field types.
|
||||
|
||||
It will add both: validation in `pydantic` model and will display available options in schema,
|
||||
therefore it will be available in docs of `fastapi`.
|
||||
|
||||
If you still want to use `Enum` in your application you can do this by passing a `Enum` into choices
|
||||
and later pass value of given option to a given field (note that Enum is not JsonSerializable).
|
||||
|
||||
```python
|
||||
# note that imports and endpoints declaration
|
||||
# is skipped here for brevity
|
||||
from enum import Enum
|
||||
class TestEnum(Enum):
|
||||
val1 = 'Val1'
|
||||
val2 = 'Val2'
|
||||
|
||||
class TestModel(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "org"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
# pass list(Enum) to choices
|
||||
enum_string: str = ormar.String(max_length=100, choices=list(TestEnum))
|
||||
|
||||
# sample payload coming to fastapi
|
||||
response = client.post(
|
||||
"/test_models/",
|
||||
json={
|
||||
"id": 1,
|
||||
# you need to refer to the value of the `Enum` option
|
||||
# if called like this, alternatively just use value
|
||||
# string "Val1" in this case
|
||||
"enum_string": TestEnum.val1.value
|
||||
},
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
[relations]: ../relations/index.md
|
||||
[queries]: ../queries.md
|
||||
|
||||
@ -22,17 +22,14 @@ If you set a field as `Optional`, it defaults to `None` if not provided and that
|
||||
exactly what's going to happen during loading from database.
|
||||
|
||||
```python
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
class ModelTest(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=200)
|
||||
@ -57,17 +54,14 @@ By setting a default value, this value will be set on initialization and databas
|
||||
Note that setting a default to `None` is the same as setting the field to `Optional`.
|
||||
|
||||
```python
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
class ModelTest(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=200)
|
||||
@ -97,13 +91,12 @@ on initialization and each database load.
|
||||
from pydantic import Field, PaymentCardNumber
|
||||
# ...
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
)
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
CARD_NUMBERS = [
|
||||
"123456789007",
|
||||
@ -119,8 +112,7 @@ def get_number():
|
||||
|
||||
|
||||
class ModelTest2(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=200)
|
||||
@ -149,13 +141,12 @@ You can provide a value for the field in your `__init__()` method before calling
|
||||
from pydantic import BaseModel
|
||||
# ...
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
)
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
class PydanticTest(BaseModel):
|
||||
aa: str
|
||||
@ -163,8 +154,7 @@ class PydanticTest(BaseModel):
|
||||
|
||||
|
||||
class ModelTest3(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
# provide your custom init function
|
||||
def __init__(self, **kwargs):
|
||||
@ -192,4 +182,4 @@ assert test_check.pydantic_test.aa == "random"
|
||||
```
|
||||
|
||||
!!!warning
|
||||
If you do not provide a value in one of the above ways `ValidationError` will be raised on load from database.
|
||||
If you do not provide a value in one of the above ways `ValidationError` will be raised on load from database.
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
### Overview
|
||||
|
||||
The `ormar` package is an async mini ORM for Python, with support for **Postgres,
|
||||
The `ormar` package is an async ORM for Python, with support for **Postgres,
|
||||
MySQL**, and **SQLite**.
|
||||
|
||||
The main benefits of using `ormar` are:
|
||||
@ -53,13 +53,7 @@ Yet remember that those are - well - tests and not all solutions are suitable to
|
||||
|
||||
### Part of the `fastapi` ecosystem
|
||||
|
||||
As part of the fastapi ecosystem `ormar` is supported in libraries that somehow work with databases.
|
||||
|
||||
As of now `ormar` is supported by:
|
||||
|
||||
* [`fastapi-users`](https://github.com/frankie567/fastapi-users)
|
||||
* [`fastapi-crudrouter`](https://github.com/awtkns/fastapi-crudrouter)
|
||||
* [`fastapi-pagination`](https://github.com/uriyyo/fastapi-pagination)
|
||||
As part of the fastapi ecosystem `ormar` is supported in selected libraries that somehow work with databases.
|
||||
|
||||
Ormar remains sql dialect agnostic - so only columns working in all supported backends are implemented.
|
||||
|
||||
@ -76,7 +70,6 @@ Ormar is built with:
|
||||
* [`sqlalchemy core`][sqlalchemy-core] for query building.
|
||||
* [`databases`][databases] for cross-database async support.
|
||||
* [`pydantic`][pydantic] for data validation.
|
||||
* `typing_extensions` for python 3.6 - 3.7
|
||||
|
||||
### License
|
||||
|
||||
@ -128,17 +121,20 @@ For tests and basic applications the `sqlalchemy` is more than enough:
|
||||
# 1. Imports
|
||||
import sqlalchemy
|
||||
import databases
|
||||
import ormar
|
||||
|
||||
# 2. Initialization
|
||||
DATABASE_URL = "sqlite:///db.sqlite"
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
engine=sqlalchemy.create_engine(DATABASE_URL),
|
||||
)
|
||||
|
||||
# Define models here
|
||||
|
||||
# 3. Database creation and tables creation
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
metadata.create_all(engine)
|
||||
base_ormar_config.metadata.create_all(engine)
|
||||
```
|
||||
|
||||
For a sample configuration of alembic and more information regarding migrations and
|
||||
@ -175,45 +171,39 @@ Note that you can find the same script in examples folder on github.
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
DATABASE_URL = "sqlite:///db.sqlite"
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
# note that this step is optional -> all ormar cares is a internal
|
||||
# class with name Meta and proper parameters, but this way you do not
|
||||
# have to repeat the same parameters if you use only one database
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL),
|
||||
)
|
||||
|
||||
# note that this step is optional -> all ormar cares is a field with name
|
||||
# ormar_config # and proper parameters, but this way you do not have to repeat
|
||||
# the same parameters if you use only one database
|
||||
#
|
||||
# Note that all type hints are optional
|
||||
# below is a perfectly valid model declaration
|
||||
# class Author(ormar.Model):
|
||||
# class Meta(BaseMeta):
|
||||
# tablename = "authors"
|
||||
# ormar_config = base_ormar_config.copy(tablename="authors")
|
||||
#
|
||||
# id = ormar.Integer(primary_key=True) # <= notice no field types
|
||||
# name = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy(tablename="authors")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
ormar_config = base_ormar_config.copy(tablename="books")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
@ -224,10 +214,9 @@ class Book(ormar.Model):
|
||||
# create the database
|
||||
# note that in production you should use migrations
|
||||
# note that this is not required if you connect to existing database
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
# just to be sure we clear the db before
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
base_ormar_config.metadata.drop_all(engine)
|
||||
base_ormar_config.metadata.create_all(engine)
|
||||
|
||||
|
||||
# all functions below are divided into functionality categories
|
||||
@ -662,9 +651,7 @@ The following keyword arguments are supported on all field types.
|
||||
* `server_default: Any`
|
||||
* `index: bool`
|
||||
* `unique: bool`
|
||||
* `choices: typing.Sequence`
|
||||
* `name: str`
|
||||
* `pydantic_only: bool`
|
||||
|
||||
All fields are required unless one of the following is set:
|
||||
|
||||
@ -674,7 +661,6 @@ All fields are required unless one of the following is set:
|
||||
* `server_default` - Set a default value for the field on server side (like sqlalchemy's `func.now()`). **Not available for relation fields**
|
||||
* `primary key` with `autoincrement` - When a column is set to primary key and autoincrement is set on this column.
|
||||
Autoincrement is set by default on int primary keys.
|
||||
* `pydantic_only` - Field is available only as normal pydantic field, not stored in the database.
|
||||
|
||||
### Available signals
|
||||
|
||||
|
||||
@ -13,22 +13,25 @@ Ormar uses `databases` for connectivity issues, `pydantic` for validation and `s
|
||||
All three should install along the installation of ormar if not present at your system before.
|
||||
|
||||
* databases
|
||||
* pydantic>=1.5
|
||||
* pydantic
|
||||
* sqlalchemy
|
||||
|
||||
The required versions are pinned in the pyproject.toml file.
|
||||
|
||||
## Optional dependencies
|
||||
|
||||
*ormar* has three optional dependencies based on database backend you use:
|
||||
|
||||
### Postgresql
|
||||
### Database backend
|
||||
|
||||
#### Postgresql
|
||||
|
||||
```py
|
||||
pip install ormar[postgresql]
|
||||
```
|
||||
Will install also `asyncpg` and `psycopg2`.
|
||||
|
||||
### Mysql
|
||||
#### Mysql
|
||||
|
||||
```py
|
||||
pip install ormar[mysql]
|
||||
@ -36,7 +39,7 @@ pip install ormar[mysql]
|
||||
|
||||
Will install also `aiomysql` and `pymysql`.
|
||||
|
||||
### Sqlite
|
||||
#### Sqlite
|
||||
|
||||
```py
|
||||
pip install ormar[sqlite]
|
||||
|
||||
330
docs/migration.md
Normal file
330
docs/migration.md
Normal file
@ -0,0 +1,330 @@
|
||||
# Migration to 0.20.0 based on pydantic 2.X.X
|
||||
|
||||
Version 0.20.0 provides support for pydantic v2.X.X that provides significant speed boost (validation and serialization is written in rust) and cleaner api for developers,
|
||||
at the same time it drops support for pydantic v.1.X.X. There are changes in `ormar` interface corresponding to changes made in `pydantic`.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
Migration to version >= 0.20.0 requires several changes in order to work properly.
|
||||
|
||||
## `ormar` Model configuration
|
||||
|
||||
Instead of defining a `Meta` class now each of the ormar models require an ormar_config parameter that is an instance of the `OrmarConfig` class.
|
||||
Note that the attribute must be named `ormar_config` and be an instance of the config class.
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
# ormar < 0.20
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
tablename = "albums"
|
||||
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
favorite: bool = ormar.Boolean(default=False)
|
||||
|
||||
# ormar >= 0.20
|
||||
class AlbumV20(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="albums_v20"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
favorite: bool = ormar.Boolean(default=False)
|
||||
```
|
||||
|
||||
### `OrmarConfig` api/ parameters
|
||||
|
||||
The `ormar_config` expose the same set of settings as `Meta` class used to provide.
|
||||
That means that you can use any of the following parameters initializing the config:
|
||||
|
||||
```python
|
||||
metadata: Optional[sqlalchemy.MetaData]
|
||||
database: Optional[databases.Database]
|
||||
engine: Optional[sqlalchemy.engine.Engine]
|
||||
tablename: Optional[str]
|
||||
order_by: Optional[List[str]]
|
||||
abstract: bool
|
||||
exclude_parent_fields: Optional[List[str]]
|
||||
queryset_class: Type[QuerySet]
|
||||
extra: Extra
|
||||
constraints: Optional[List[ColumnCollectionConstraint]]
|
||||
```
|
||||
|
||||
### `BaseMeta` equivalent - best practice
|
||||
|
||||
Note that to reduce the duplication of code and ease of development it's still recommended to create a base config and provide each of the models with a copy.
|
||||
OrmarConfig provides a convenient `copy` method for that purpose.
|
||||
|
||||
The `copy` method accepts the same parameters as `OrmarConfig` init, so you can overwrite if needed, but by default it will return already existing attributes, except for: `tablename`, `order_by` and `constraints` which by default are cleared.
|
||||
|
||||
```python hl_lines="5-8 11 20"
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database("sqlite:///db.sqlite"),
|
||||
metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
class AlbumV20(ormar.Model):
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="albums_v20"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class TrackV20(ormar.Model):
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="tracks_v20"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
```
|
||||
|
||||
## `choices` Field parameter is no longer supported.
|
||||
|
||||
Before version 0.20 you could provide `choices` parameter to any existing ormar Field to limit the accepted values.
|
||||
This functionality was dropped, and you should use `ormar.Enum` field that was designed for this purpose.
|
||||
If you want to keep the database field type (i.e. an Integer field) you can always write a custom validator.
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
# ormar < 0.20
|
||||
class Artist(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
country: str = ormar.String(default=False, max_length=50, choices=["UK", "US", "Vietnam", "Colombia"])
|
||||
|
||||
# ormar >= 0.20
|
||||
from enum import Enum
|
||||
|
||||
class Country(str, Enum):
|
||||
UK = "UK"
|
||||
US = "US"
|
||||
VIETNAM = "Vietnam"
|
||||
COLOMBIA = "Colombia"
|
||||
|
||||
class ArtistV20(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="artists_v20"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
country: Country = ormar.Enum(enum_class=Country)
|
||||
```
|
||||
|
||||
|
||||
## `pydantic_only` Field parameter is no longer supported
|
||||
|
||||
`pydantic_only` fields were already deprecated and are removed in v 0.20. Ormar allows defining pydantic fields as in ordinary pydantic model.
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
# ormar < 0.20
|
||||
class Dish(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
tablename = "dishes"
|
||||
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
cook: str = ormar.String(max_length=40, pydantic_only=True, default="sam")
|
||||
|
||||
# ormar >= 0.20
|
||||
class DishV20(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="dishes_v20"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
cook: str = "sam" # this is normal pydantic field
|
||||
```
|
||||
|
||||
## `property_field` decorator is no longer supported
|
||||
|
||||
`property_field` decorator was used to provide a way to pass calculated fields that were included in dictionary/ serialized json representation of the model.
|
||||
Version 2.X of pydantic introduced such a possibility, so you should now switch to the one native to the pydantic.
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
import pydantic
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
# ormar < 0.20
|
||||
class Employee(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
first_name: str = ormar.String(max_length=100)
|
||||
last_name: str = ormar.String(max_length=100)
|
||||
|
||||
@ormar.property_field()
|
||||
def full_name(self) -> str:
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
# ormar >= 0.20
|
||||
class EmployeeV20(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
first_name: str = ormar.String(max_length=100)
|
||||
last_name: str = ormar.String(max_length=100)
|
||||
|
||||
@pydantic.computed_field()
|
||||
def full_name(self) -> str:
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
```
|
||||
|
||||
## Deprecated methods
|
||||
|
||||
All methods listed below are deprecated and will be removed in version 0.30 of `ormar`.
|
||||
|
||||
### `dict()` becomes the `model_dump()`
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
class Album(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="albums"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
favorite: bool = ormar.Boolean(default=False)
|
||||
|
||||
album = Album(name="Dark Side of the Moon")
|
||||
|
||||
# ormar < 0.20
|
||||
album_dict = album.dict()
|
||||
|
||||
# ormar >= 0.20
|
||||
new_album_dict = album.model_dump()
|
||||
```
|
||||
|
||||
Note that parameters remain the same i.e. `include`, `exclude` etc.
|
||||
|
||||
### `json()` becomes the `model_dump_json()`
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
class Album(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="albums"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
favorite: bool = ormar.Boolean(default=False)
|
||||
|
||||
album = Album(name="Dark Side of the Moon")
|
||||
|
||||
# ormar < 0.20
|
||||
album_json= album.json()
|
||||
|
||||
# ormar >= 0.20
|
||||
new_album_dict = album.model_dump_json()
|
||||
```
|
||||
|
||||
Note that parameters remain the same i.e. `include`, `exclude` etc.
|
||||
|
||||
### `construct()` becomes the `model_construct()`
|
||||
|
||||
```python
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
class Album(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="albums"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
favorite: bool = ormar.Boolean(default=False)
|
||||
|
||||
params = {
|
||||
"name": "Dark Side of the Moon",
|
||||
"favorite": True,
|
||||
}
|
||||
# ormar < 0.20
|
||||
album = Album.construct(**params)
|
||||
|
||||
# ormar >= 0.20
|
||||
album = Album.model_construct(**params)
|
||||
```
|
||||
|
||||
To read more about construct please refer to `pydantic` documentation.
|
||||
@ -9,7 +9,7 @@ They are being managed in the background and you do not have to create them on y
|
||||
|
||||
To build an ormar model you simply need to inherit a `ormar.Model` class.
|
||||
|
||||
```Python hl_lines="10"
|
||||
```Python hl_lines="9"
|
||||
--8<-- "../docs_src/models/docs001.py"
|
||||
```
|
||||
|
||||
@ -23,7 +23,7 @@ Each table **has to** have a primary key column, which you specify by setting `p
|
||||
|
||||
Only one primary key column is allowed.
|
||||
|
||||
```Python hl_lines="15 16 17"
|
||||
```Python hl_lines="15-17"
|
||||
--8<-- "../docs_src/models/docs001.py"
|
||||
```
|
||||
|
||||
@ -42,15 +42,15 @@ id: int = ormar.Integer(primary_key=True, autoincrement=False)
|
||||
#### Non Database Fields
|
||||
|
||||
Note that if you need a normal pydantic field in your model (used to store value on model or pass around some value) you can define a
|
||||
field with parameter `pydantic_only=True`.
|
||||
field like usual in pydantic.
|
||||
|
||||
Fields created like this are added to the `pydantic` model fields -> so are subject to validation according to `Field` type,
|
||||
also appear in `dict()` and `json()` result.
|
||||
also appear in `model_dump()` and `model_dump_json()` result.
|
||||
|
||||
The difference is that **those fields are not saved in the database**. So they won't be included in underlying sqlalchemy `columns`,
|
||||
or `table` variables (check [Internals][Internals] section below to see how you can access those if you need).
|
||||
|
||||
Subsequently `pydantic_only` fields won't be included in migrations or any database operation (like `save`, `update` etc.)
|
||||
Subsequently, pydantic fields won't be included in migrations or any database operation (like `save`, `update` etc.)
|
||||
|
||||
Fields like those can be passed around into payload in `fastapi` request and will be returned in `fastapi` response
|
||||
(of course only if you set their value somewhere in your code as the value is **not** fetched from the db.
|
||||
@ -58,30 +58,32 @@ If you pass a value in `fastapi` `request` and return the same instance that `fa
|
||||
you should get back exactly same value in `response`.).
|
||||
|
||||
!!!warning
|
||||
`pydantic_only=True` fields are always **Optional** and it cannot be changed (otherwise db load validation would fail)
|
||||
pydantic fields have to be always **Optional** and it cannot be changed (otherwise db load validation would fail)
|
||||
|
||||
!!!tip
|
||||
`pydantic_only=True` fields are a good solution if you need to pass additional information from outside of your API
|
||||
(i.e. frontend). They are not stored in db but you can access them in your `APIRoute` code and they also have `pydantic` validation.
|
||||
|
||||
```Python hl_lines="18"
|
||||
```Python hl_lines="19"
|
||||
--8<-- "../docs_src/models/docs014.py"
|
||||
```
|
||||
|
||||
If you combine `pydantic_only=True` field with `default` parameter and do not pass actual value in request you will always get default value.
|
||||
If you set pydantic field with `default` parameter and do not pass actual value in request you will always get default value.
|
||||
Since it can be a function you can set `default=datetime.datetime.now` and get current timestamp each time you call an endpoint etc.
|
||||
|
||||
#### Non Database Fields in Fastapi
|
||||
|
||||
!!!note
|
||||
Note that both `pydantic_only` and `property_field` decorated field can be included/excluded in both `dict()` and `fastapi`
|
||||
Note, that both pydantic and calculated_fields decorated field can be included/excluded in both `model_dump()` and `fastapi`
|
||||
response with `include`/`exclude` and `response_model_include`/`response_model_exclude` accordingly.
|
||||
|
||||
```python
|
||||
# <==related of code removed for clarity==>
|
||||
# <==part of related code removed for clarity==>
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
engine=sqlalchemy.create_engine(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta:
|
||||
tablename: str = "users2"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="users2")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
email: str = ormar.String(max_length=255, nullable=False)
|
||||
@ -89,18 +91,18 @@ class User(ormar.Model):
|
||||
first_name: str = ormar.String(max_length=255)
|
||||
last_name: str = ormar.String(max_length=255)
|
||||
category: str = ormar.String(max_length=255, nullable=True)
|
||||
timestamp: datetime.datetime = ormar.DateTime(
|
||||
pydantic_only=True, default=datetime.datetime.now
|
||||
timestamp: datetime.datetime = pydantic.Field(
|
||||
default=datetime.datetime.now
|
||||
)
|
||||
|
||||
# <==related of code removed for clarity==>
|
||||
app =FastAPI()
|
||||
# <==part of related code removed for clarity==>
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/users/")
|
||||
async def create_user(user: User):
|
||||
return await user.save()
|
||||
|
||||
# <==related of code removed for clarity==>
|
||||
# <==part of related code removed for clarity==>
|
||||
|
||||
def test_excluding_fields_in_endpoints():
|
||||
client = TestClient(app)
|
||||
@ -127,121 +129,7 @@ def test_excluding_fields_in_endpoints():
|
||||
assert response.json().get("timestamp") == str(timestamp).replace(" ", "T")
|
||||
|
||||
|
||||
# <==related of code removed for clarity==>
|
||||
```
|
||||
|
||||
#### Property fields
|
||||
|
||||
Sometimes it's desirable to do some kind of calculation on the model instance. One of the most common examples can be concatenating
|
||||
two or more fields. Imagine you have `first_name` and `last_name` fields on your model, but would like to have `full_name` in the result
|
||||
of the `fastapi` query.
|
||||
|
||||
You can create a new `pydantic` model with a `method` that accepts only `self` (so like default python `@property`)
|
||||
and populate it in your code.
|
||||
|
||||
But it's so common that `ormar` has you covered. You can "materialize" a `property_field` on you `Model`.
|
||||
|
||||
!!!warning
|
||||
`property_field` fields are always **Optional** and it cannot be changed (otherwise db load validation would fail)
|
||||
|
||||
```Python hl_lines="20-22"
|
||||
--8<-- "../docs_src/models/docs015.py"
|
||||
```
|
||||
|
||||
!!!warning
|
||||
The decorated function has to accept only one parameter, and that parameter have to be `self`.
|
||||
|
||||
If you try to decorate a function with more parameters `ormar` will raise `ModelDefinitionError`.
|
||||
|
||||
Sample:
|
||||
|
||||
```python
|
||||
# will raise ModelDefinitionError
|
||||
@property_field
|
||||
def prefixed_name(self, prefix="prefix_"):
|
||||
return 'custom_prefix__' + self.name
|
||||
|
||||
# will raise ModelDefinitionError
|
||||
# (calling first param something else than 'self' is a bad practice anyway)
|
||||
@property_field
|
||||
def prefixed_name(instance):
|
||||
return 'custom_prefix__' + self.name
|
||||
```
|
||||
|
||||
Note that `property_field` decorated methods do not go through verification (but that might change in future) and are only available
|
||||
in the response from `fastapi` and `dict()` and `json()` methods. You cannot pass a value for this field in the request
|
||||
(or rather you can but it will be discarded by ormar so really no point but no Exception will be raised).
|
||||
|
||||
!!!note
|
||||
Note that both `pydantic_only` and `property_field` decorated field can be included/excluded in both `dict()` and `fastapi`
|
||||
response with `include`/`exclude` and `response_model_include`/`response_model_exclude` accordingly.
|
||||
|
||||
!!!tip
|
||||
Note that `@property_field` decorator is designed to replace the python `@property` decorator, you do not have to combine them.
|
||||
|
||||
In theory you can cause `ormar` have a failsafe mechanism, but note that i.e. `mypy` will complain about re-decorating a property.
|
||||
|
||||
```python
|
||||
# valid and working but unnecessary and mypy will complain
|
||||
@property_field
|
||||
@property
|
||||
def prefixed_name(self):
|
||||
return 'custom_prefix__' + self.name
|
||||
```
|
||||
|
||||
```python
|
||||
# <==related of code removed for clarity==>
|
||||
def gen_pass(): # note: NOT production ready
|
||||
choices = string.ascii_letters + string.digits + "!@#$%^&*()"
|
||||
return "".join(random.choice(choices) for _ in range(20))
|
||||
|
||||
class RandomModel(ormar.Model):
|
||||
class Meta:
|
||||
tablename: str = "random_users"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
include_props_in_dict = True
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
password: str = ormar.String(max_length=255, default=gen_pass)
|
||||
first_name: str = ormar.String(max_length=255, default="John")
|
||||
last_name: str = ormar.String(max_length=255)
|
||||
created_date: datetime.datetime = ormar.DateTime(
|
||||
server_default=sqlalchemy.func.now()
|
||||
)
|
||||
|
||||
@property_field
|
||||
def full_name(self) -> str:
|
||||
return " ".join([self.first_name, self.last_name])
|
||||
|
||||
# <==related of code removed for clarity==>
|
||||
app =FastAPI()
|
||||
|
||||
# explicitly exclude property_field in this endpoint
|
||||
@app.post("/random/", response_model=RandomModel, response_model_exclude={"full_name"})
|
||||
async def create_user(user: RandomModel):
|
||||
return await user.save()
|
||||
|
||||
# <==related of code removed for clarity==>
|
||||
|
||||
def test_excluding_property_field_in_endpoints2():
|
||||
client = TestClient(app)
|
||||
with client as client:
|
||||
RandomModel.Meta.include_props_in_dict = True
|
||||
user3 = {"last_name": "Test"}
|
||||
response = client.post("/random3/", json=user3)
|
||||
assert list(response.json().keys()) == [
|
||||
"id",
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"created_date",
|
||||
]
|
||||
# despite being decorated with property_field if you explicitly exclude it it will be gone
|
||||
assert response.json().get("full_name") is None
|
||||
|
||||
# <==related of code removed for clarity==>
|
||||
# <==part of related code removed for clarity==>
|
||||
```
|
||||
|
||||
#### Fields names vs Column names
|
||||
@ -252,25 +140,25 @@ If for whatever reason you prefer to change the name in the database but keep th
|
||||
with specifying `name` parameter during Field declaration
|
||||
|
||||
Here you have a sample model with changed names
|
||||
```Python hl_lines="16-19"
|
||||
```Python hl_lines="18-21"
|
||||
--8<-- "../docs_src/models/docs008.py"
|
||||
```
|
||||
|
||||
Note that you can also change the ForeignKey column name
|
||||
```Python hl_lines="21"
|
||||
```Python hl_lines="34"
|
||||
--8<-- "../docs_src/models/docs009.py"
|
||||
```
|
||||
|
||||
But for now you cannot change the ManyToMany column names as they go through other Model anyway.
|
||||
```Python hl_lines="28"
|
||||
```Python hl_lines="43"
|
||||
--8<-- "../docs_src/models/docs010.py"
|
||||
```
|
||||
|
||||
## Overwriting the default QuerySet
|
||||
### Overwriting the default QuerySet
|
||||
|
||||
If you want to customize the queries run by ormar you can define your own queryset class (that extends the ormar `QuerySet`) in your model class, default one is simply the `QuerySet`
|
||||
|
||||
You can provide a new class in `Meta` configuration of your class as `queryset_class` parameter.
|
||||
You can provide a new class in `ormar_config` of your class as `queryset_class` parameter.
|
||||
|
||||
```python
|
||||
import ormar
|
||||
@ -288,12 +176,10 @@ class MyQuerySetClass(QuerySet):
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
|
||||
class Meta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
tablename = "book"
|
||||
queryset_class = MyQuerySetClass
|
||||
ormar_config = base_ormar_config.copy(
|
||||
queryset_class=MyQuerySetClass,
|
||||
tablename="book",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=32)
|
||||
@ -304,17 +190,9 @@ book = await Book.objects.first_or_404(name="123")
|
||||
|
||||
```
|
||||
|
||||
### Type Hints & Legacy
|
||||
### Type Hints
|
||||
|
||||
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.
|
||||
Note that for better IDE support and mypy checks you can provide type hints.
|
||||
|
||||
```Python hl_lines="15-17"
|
||||
--8<-- "../docs_src/models/docs001.py"
|
||||
@ -343,9 +221,9 @@ and table creation you need to assign each `Model` with two special parameters.
|
||||
|
||||
One is `Database` instance created with your database url in [sqlalchemy connection string][sqlalchemy connection string] format.
|
||||
|
||||
Created instance needs to be passed to every `Model` with `Meta` class `database` parameter.
|
||||
Created instance needs to be passed to every `Model` with `ormar_config` object `database` parameter.
|
||||
|
||||
```Python hl_lines="1 6 12"
|
||||
```Python hl_lines="1 5 11"
|
||||
--8<-- "../docs_src/models/docs001.py"
|
||||
```
|
||||
|
||||
@ -357,9 +235,9 @@ Created instance needs to be passed to every `Model` with `Meta` class `database
|
||||
|
||||
Second dependency is sqlalchemy `MetaData` instance.
|
||||
|
||||
Created instance needs to be passed to every `Model` with `Meta` class `metadata` parameter.
|
||||
Created instance needs to be passed to every `Model` with `ormar_config` object `metadata` parameter.
|
||||
|
||||
```Python hl_lines="2 7 13"
|
||||
```Python hl_lines="3 6 12"
|
||||
--8<-- "../docs_src/models/docs001.py"
|
||||
```
|
||||
|
||||
@ -369,25 +247,22 @@ Created instance needs to be passed to every `Model` with `Meta` class `metadata
|
||||
|
||||
#### Best practice
|
||||
|
||||
Only thing that `ormar` expects is a class with name `Meta` and two class variables: `metadata` and `databases`.
|
||||
Note that `ormar` expects the field with name `ormar_config` that is an instance of `OrmarConfig` class.
|
||||
To ease the config management, the `OrmarConfig` class provide `copy` method.
|
||||
So instead of providing the same parameters over and over again for all models
|
||||
you should create a base object and use its copy in all models.
|
||||
|
||||
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"
|
||||
```Python hl_lines="9-12 19 28"
|
||||
--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
|
||||
|
||||
By default table name is created from Model class name as lowercase name plus 's'.
|
||||
|
||||
You can overwrite this parameter by providing `Meta` class `tablename` argument.
|
||||
You can overwrite this parameter by providing `ormar_config` object's `tablename` argument.
|
||||
|
||||
```Python hl_lines="12 13 14"
|
||||
```Python hl_lines="14-16"
|
||||
--8<-- "../docs_src/models/docs002.py"
|
||||
```
|
||||
|
||||
@ -395,74 +270,72 @@ You can overwrite this parameter by providing `Meta` class `tablename` argument.
|
||||
|
||||
On a model level you can also set model-wise constraints on sql columns.
|
||||
|
||||
Right now only `IndexColumns` and `UniqueColumns` constraints are supported.
|
||||
Right now only `IndexColumns`, `UniqueColumns` and `CheckColumns` constraints are supported.
|
||||
|
||||
!!!note
|
||||
Note that both constraints should be used only if you want to set a name on constraint or want to set the index on multiple columns, otherwise `index` and `unique` properties on ormar fields are preferred.
|
||||
Note that both constraints should be used only if you want to set a name on constraint or want to set the index on multiple columns, otherwise `index` and `unique` properties on ormar fields are preferred.
|
||||
|
||||
!!!tip
|
||||
To read more about columns constraints like `primary_key`, `unique`, `ForeignKey` etc. visit [fields][fields].
|
||||
|
||||
#### UniqueColumns
|
||||
|
||||
You can set this parameter by providing `Meta` class `constraints` argument.
|
||||
You can set this parameter by providing `ormar_config` object `constraints` argument.
|
||||
|
||||
```Python hl_lines="14-17"
|
||||
```Python hl_lines="13-16"
|
||||
--8<-- "../docs_src/models/docs006.py"
|
||||
```
|
||||
|
||||
!!!note
|
||||
Note that constraints are meant for combination of columns that should be unique.
|
||||
To set one column as unique use [`unique`](../fields/common-parameters.md#unique) common parameter.
|
||||
Of course you can set many columns as unique with this param but each of them will be checked separately.
|
||||
Note that constraints are meant for combination of columns that should be unique.
|
||||
To set one column as unique use [`unique`](../fields/common-parameters.md#unique) common parameter.
|
||||
Of course you can set many columns as unique with this param but each of them will be checked separately.
|
||||
|
||||
#### IndexColumns
|
||||
|
||||
You can set this parameter by providing `Meta` class `constraints` argument.
|
||||
You can set this parameter by providing `ormar_config` object `constraints` argument.
|
||||
|
||||
```Python hl_lines="14-17"
|
||||
```Python hl_lines="13-16"
|
||||
--8<-- "../docs_src/models/docs017.py"
|
||||
```
|
||||
|
||||
!!!note
|
||||
Note that constraints are meant for combination of columns that should be in the index.
|
||||
To set one column index use [`unique`](../fields/common-parameters.md#index) common parameter.
|
||||
Of course, you can set many columns as indexes with this param but each of them will be a separate index.
|
||||
Note that constraints are meant for combination of columns that should be in the index.
|
||||
To set one column index use [`unique`](../fields/common-parameters.md#index) common parameter.
|
||||
Of course, you can set many columns as indexes with this param but each of them will be a separate index.
|
||||
|
||||
#### CheckColumns
|
||||
|
||||
You can set this parameter by providing `Meta` class `constraints` argument.
|
||||
You can set this parameter by providing `ormar_config` object `constraints` argument.
|
||||
|
||||
```Python hl_lines="14-17"
|
||||
```Python hl_lines="15-20"
|
||||
--8<-- "../docs_src/models/docs018.py"
|
||||
```
|
||||
|
||||
!!!note
|
||||
Note that some databases do not actively support check constraints such as MySQL.
|
||||
Note that some databases do not actively support check constraints (such as MySQL).
|
||||
|
||||
|
||||
### Pydantic configuration
|
||||
|
||||
As each `ormar.Model` is also a `pydantic` model, you might want to tweak the settings of the pydantic configuration.
|
||||
|
||||
The way to do this in pydantic is to adjust the settings on the `Config` class provided to your model, and it works exactly the same for ormar models.
|
||||
The way to do this in pydantic is to adjust the settings on the `model_config` dictionary provided to your model, and it works exactly the same for ormar models.
|
||||
|
||||
So in order to set your own preferences you need to provide not only the `Meta` class but also the `Config` class to your model.
|
||||
So in order to set your own preferences you need to provide not only the `ormar_config` class but also the `model_config = ConfigDict()` class to your model.
|
||||
|
||||
!!!note
|
||||
To read more about available settings visit the [pydantic](https://pydantic-docs.helpmanual.io/usage/model_config/) config page.
|
||||
To read more about available settings visit the [pydantic](https://pydantic-docs.helpmanual.io/usage/model_config/) config page.
|
||||
|
||||
Note that if you do not provide your own configuration, ormar will do it for you.
|
||||
The default config provided is as follows:
|
||||
|
||||
```python
|
||||
class Config(pydantic.BaseConfig):
|
||||
orm_mode = True
|
||||
validate_assignment = True
|
||||
model_config = ConfigDict(validate_assignment=True, ser_json_bytes="base64")
|
||||
```
|
||||
|
||||
So to overwrite setting or provide your own a sample model can look like following:
|
||||
```Python hl_lines="15-16"
|
||||
```Python hl_lines="16"
|
||||
--8<-- "../docs_src/models/docs016.py"
|
||||
```
|
||||
|
||||
@ -474,69 +347,64 @@ If you try to do so the `ModelError` will be raised.
|
||||
|
||||
Since the extra fields cannot be saved in the database the default to disallow such fields seems a feasible option.
|
||||
|
||||
On the contrary in `pydantic` the default option is to ignore such extra fields, therefore `ormar` provides an `Meta.extra` setting to behave in the same way.
|
||||
On the contrary in `pydantic` the default option is to ignore such extra fields, therefore `ormar` provides an `ormar_config.extra` setting to behave in the same way.
|
||||
|
||||
To ignore extra fields passed to `ormar` set this setting to `Extra.ignore` instead of default `Extra.forbid`.
|
||||
|
||||
Note that `ormar` does not allow accepting extra fields, you can only ignore them or forbid them (raise exception if present)
|
||||
|
||||
```python
|
||||
from ormar import Extra
|
||||
from ormar import Extra, OrmarConfig
|
||||
|
||||
class Child(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "children"
|
||||
metadata = metadata
|
||||
database = database
|
||||
extra = Extra.ignore # set extra setting to prevent exceptions on extra fields presence
|
||||
ormar_config = OrmarConfig(
|
||||
tablename="children",
|
||||
extra=Extra.ignore # set extra setting to prevent exceptions on extra fields presence
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(name="child_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
last_name: str = ormar.String(name="lname", max_length=100)
|
||||
```
|
||||
|
||||
To set the same setting on all model check the [best practices]("../models/index/#best-practice") and `BaseMeta` concept.
|
||||
To set the same setting on all model check the [best practices]("../models/index/#best-practice") and `base_ormar_config` concept.
|
||||
|
||||
## Model sort order
|
||||
|
||||
When querying the database with given model by default the Model is ordered by the `primary_key`
|
||||
column ascending. If you wish to change the default behaviour you can do it by providing `orders_by`
|
||||
parameter to model `Meta` class.
|
||||
parameter to model `ormar_config` object.
|
||||
|
||||
Sample default ordering:
|
||||
Sample default ordering (not specified - so by primary key):
|
||||
```python
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
# default sort by column id ascending
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="authors",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
```
|
||||
Modified
|
||||
```python
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
```python hl_lines="9"
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
# now default sort by name descending
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
orders_by = ["-name"]
|
||||
ormar_config = base_ormar_config.copy(
|
||||
orders_by = ["-name"],
|
||||
tablename="authors",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -546,12 +414,9 @@ class Author(ormar.Model):
|
||||
|
||||
There are two ways to create and persist the `Model` instance in the database.
|
||||
|
||||
!!!tip
|
||||
Use `ipython` to try this from the console, since it supports `await`.
|
||||
|
||||
If you plan to modify the instance in the later execution of your program you can initiate your `Model` as a normal class and later await a `save()` call.
|
||||
|
||||
```Python hl_lines="20 21"
|
||||
```Python hl_lines="25-26"
|
||||
--8<-- "../docs_src/models/docs007.py"
|
||||
```
|
||||
|
||||
@ -561,7 +426,7 @@ For creating multiple objects at once a `bulk_create()` QuerySet's method is ava
|
||||
|
||||
Each model has a `QuerySet` initialised as `objects` parameter
|
||||
|
||||
```Python hl_lines="23"
|
||||
```Python hl_lines="28"
|
||||
--8<-- "../docs_src/models/docs007.py"
|
||||
```
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ Out of various types of ORM models inheritance `ormar` currently supports two of
|
||||
|
||||
## Types of inheritance
|
||||
|
||||
The short summary of different types of inheritance is:
|
||||
The short summary of different types of inheritance:
|
||||
|
||||
* **Mixins [SUPPORTED]** - don't subclass `ormar.Model`, just define fields that are
|
||||
later used on different models (like `created_date` and `updated_date` on each model),
|
||||
@ -32,6 +32,13 @@ To use Mixins just define a class that is not inheriting from an `ormar.Model` b
|
||||
defining `ormar.Fields` as class variables.
|
||||
|
||||
```python
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
engine=sqlalchemy.create_engine(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
# a mixin defines the fields but is a normal python class
|
||||
class AuditMixin:
|
||||
created_by: str = ormar.String(max_length=100)
|
||||
@ -45,10 +52,7 @@ class DateFieldsMixins:
|
||||
|
||||
# a models can inherit from one or more mixins
|
||||
class Category(ormar.Model, DateFieldsMixins, AuditMixin):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -57,7 +61,7 @@ class Category(ormar.Model, DateFieldsMixins, AuditMixin):
|
||||
|
||||
!!!tip
|
||||
Note that Mixins are **not** models, so you still need to inherit
|
||||
from `ormar.Model` as well as define `Meta` class in the **final** model.
|
||||
from `ormar.Model` as well as define `ormar_config` field in the **final** model.
|
||||
|
||||
A Category class above will have four additional fields: `created_date`, `updated_date`,
|
||||
`created_by` and `updated_by`.
|
||||
@ -73,11 +77,11 @@ In concept concrete table inheritance is very similar to Mixins, but uses
|
||||
actual `ormar.Models` as base classes.
|
||||
|
||||
!!!warning
|
||||
Note that base classes have `abstract=True` set in `Meta` class, if you try
|
||||
Note that base classes have `abstract=True` set in `ormar_config` object, if you try
|
||||
to inherit from non abstract marked class `ModelDefinitionError` will be raised.
|
||||
|
||||
Since this abstract Model will never be initialized you can skip `metadata`
|
||||
and `database` in it's `Meta` definition.
|
||||
and `database` in it's `ormar_config` definition.
|
||||
|
||||
But if you provide it - it will be inherited, that way you do not have to
|
||||
provide `metadata` and `databases` in the final/concrete class
|
||||
@ -91,8 +95,7 @@ otherwise an error will be raised.
|
||||
# note that base classes have abstract=True
|
||||
# since this model will never be initialized you can skip metadata and database
|
||||
class AuditModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_by: str = ormar.String(max_length=100)
|
||||
updated_by: str = ormar.String(max_length=100, default="Sam")
|
||||
@ -100,10 +103,11 @@ class AuditModel(ormar.Model):
|
||||
|
||||
# but if you provide it it will be inherited - DRY (Don't Repeat Yourself) in action
|
||||
class DateFieldsModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(
|
||||
abstract=True,
|
||||
metadata=metadata,
|
||||
database=db,
|
||||
)
|
||||
|
||||
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
updated_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
@ -111,8 +115,7 @@ class DateFieldsModel(ormar.Model):
|
||||
|
||||
# that way you do not have to provide metadata and databases in concrete class
|
||||
class Category(DateFieldsModel, AuditModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -123,8 +126,6 @@ class Category(DateFieldsModel, AuditModel):
|
||||
The list of inherited options/settings is as follows: `metadata`, `database`
|
||||
and `constraints`.
|
||||
|
||||
Also methods decorated with `@property_field` decorator will be inherited/recognized.
|
||||
|
||||
Of course apart from that all fields from base classes are combined and created in the
|
||||
concrete table of the final Model.
|
||||
|
||||
@ -140,15 +141,16 @@ inheritance.
|
||||
Whenever you define a field with same name and new definition it will completely replace
|
||||
the previously defined one.
|
||||
|
||||
```python
|
||||
```python hl_lines="28"
|
||||
# base class
|
||||
class DateFieldsModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = OrmarConfig(
|
||||
abstract=True,
|
||||
metadata=metadata,
|
||||
database=db,
|
||||
# note that UniqueColumns need sqlalchemy db columns names not the ormar ones
|
||||
constraints = [ormar.UniqueColumns("creation_date", "modification_date")]
|
||||
constraints=[ormar.UniqueColumns("creation_date", "modification_date")]
|
||||
)
|
||||
|
||||
created_date: datetime.datetime = ormar.DateTime(
|
||||
default=datetime.datetime.now, name="creation_date"
|
||||
@ -159,10 +161,11 @@ class DateFieldsModel(ormar.Model):
|
||||
|
||||
|
||||
class RedefinedField(DateFieldsModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "redefines"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = OrmarConfig(
|
||||
tablename="redefines",
|
||||
metadata=metadata,
|
||||
database=db,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
# here the created_date is replaced by the String field
|
||||
@ -170,12 +173,12 @@ class RedefinedField(DateFieldsModel):
|
||||
|
||||
|
||||
# you can verify that the final field is correctly declared and created
|
||||
changed_field = RedefinedField.Meta.model_fields["created_date"]
|
||||
changed_field = RedefinedField.ormar_config.model_fields["created_date"]
|
||||
assert changed_field.default is None
|
||||
assert changed_field.alias == "creation_date"
|
||||
assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns)
|
||||
assert any(x.name == "creation_date" for x in RedefinedField.ormar_config.table.columns)
|
||||
assert isinstance(
|
||||
RedefinedField.Meta.table.columns["creation_date"].type,
|
||||
RedefinedField.ormar_config.table.columns["creation_date"].type,
|
||||
sqlalchemy.sql.sqltypes.String,
|
||||
)
|
||||
```
|
||||
@ -225,9 +228,7 @@ That might sound complicated but let's look at the following example:
|
||||
```python
|
||||
# normal model used in relation
|
||||
class Person(ormar.Model):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -235,10 +236,7 @@ class Person(ormar.Model):
|
||||
|
||||
# parent model - needs to be abstract
|
||||
class Car(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50)
|
||||
@ -249,16 +247,13 @@ class Car(ormar.Model):
|
||||
|
||||
|
||||
class Truck(Car):
|
||||
class Meta:
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
max_capacity: int = ormar.Integer()
|
||||
|
||||
|
||||
class Bus(Car):
|
||||
class Meta:
|
||||
# default naming is name.lower()+'s' so it's ugly for buss ;)
|
||||
tablename = "buses"
|
||||
ormar_config = base_ormar_config.copy(tablename="buses")
|
||||
|
||||
max_persons: int = ormar.Integer()
|
||||
```
|
||||
@ -266,7 +261,7 @@ class Bus(Car):
|
||||
Now when you will inspect the fields on Person model you will get:
|
||||
|
||||
```python
|
||||
Person.Meta.model_fields
|
||||
Person.ormar_config.model_fields
|
||||
"""
|
||||
{'id': <class 'ormar.fields.model_fields.Integer'>,
|
||||
'name': <class 'ormar.fields.model_fields.String'>,
|
||||
@ -293,8 +288,7 @@ different `related_name` parameter.
|
||||
```python
|
||||
# rest of the above example remains the same
|
||||
class Bus(Car):
|
||||
class Meta:
|
||||
tablename = "buses"
|
||||
ormar_config = base_ormar_config.copy(tablename="buses")
|
||||
|
||||
# new field that changes the related_name
|
||||
owner: Person = ormar.ForeignKey(Person, related_name="buses")
|
||||
@ -304,7 +298,7 @@ class Bus(Car):
|
||||
Now the columns looks much better.
|
||||
|
||||
```python
|
||||
Person.Meta.model_fields
|
||||
Person.ormar_config.model_fields
|
||||
"""
|
||||
{'id': <class 'ormar.fields.model_fields.Integer'>,
|
||||
'name': <class 'ormar.fields.model_fields.String'>,
|
||||
@ -328,7 +322,7 @@ Person.Meta.model_fields
|
||||
Similarly, you can inherit from Models that have ManyToMany relations declared but
|
||||
there is one, but substantial difference - the Through model.
|
||||
|
||||
Since in the future the Through model will be able to hold additional fields and now it links only two Tables
|
||||
Since the Through model will be able to hold additional fields, and now it links only two Tables
|
||||
(`from` and `to` ones), each child that inherits the m2m relation field has to have separate
|
||||
Through model.
|
||||
|
||||
@ -344,27 +338,18 @@ We will modify the previous example described above to use m2m relation for co_o
|
||||
```python
|
||||
# person remain the same as above
|
||||
class Person(ormar.Model):
|
||||
class Meta:
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
# new through model between Person and Car2
|
||||
class PersonsCar(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "cars_x_persons"
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename="cars_x_persons")
|
||||
|
||||
# note how co_owners is now ManyToMany relation
|
||||
class Car2(ormar.Model):
|
||||
class Meta:
|
||||
# parent class needs to be marked abstract
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50)
|
||||
@ -379,16 +364,13 @@ class Car2(ormar.Model):
|
||||
|
||||
# child models define only additional Fields
|
||||
class Truck2(Car2):
|
||||
class Meta:
|
||||
# note how you don't have to provide inherited Meta params
|
||||
tablename = "trucks2"
|
||||
ormar_config = base_ormar_config.copy(tablename="trucks2")
|
||||
|
||||
max_capacity: int = ormar.Integer()
|
||||
|
||||
|
||||
class Bus2(Car2):
|
||||
class Meta:
|
||||
tablename = "buses2"
|
||||
ormar_config = base_ormar_config.copy(tablename="buses2")
|
||||
|
||||
max_persons: int = ormar.Integer()
|
||||
```
|
||||
@ -402,7 +384,7 @@ That way for class Truck2 the relation defined in
|
||||
You can verify the names by inspecting the list of fields present on `Person` model.
|
||||
|
||||
```python
|
||||
Person.Meta.model_fields
|
||||
Person.ormar_config.model_fields
|
||||
{
|
||||
# note how all relation fields need to be unique on Person
|
||||
# regardless if autogenerated or manually overwritten
|
||||
@ -425,14 +407,14 @@ But that's not all. It's kind of internal to `ormar` but affects the data struct
|
||||
so let's examine the through models for both `Bus2` and `Truck2` models.
|
||||
|
||||
```python
|
||||
Bus2.Meta.model_fields['co_owners'].through
|
||||
Bus2.ormar_config.model_fields['co_owners'].through
|
||||
<class 'abc.PersonsCarBus2'>
|
||||
Bus2.Meta.model_fields['co_owners'].through.Meta.tablename
|
||||
Bus2.ormar_config.model_fields['co_owners'].through.ormar_config.tablename
|
||||
'cars_x_persons_buses2'
|
||||
|
||||
Truck2.Meta.model_fields['co_owners'].through
|
||||
Truck2.ormar_config.model_fields['co_owners'].through
|
||||
<class 'abc.PersonsCarTruck2'>
|
||||
Truck2.Meta.model_fields['co_owners'].through.Meta.tablename
|
||||
Truck2.ormar_config.model_fields['co_owners'].through.ormar_config.tablename
|
||||
'cars_x_persons_trucks2'
|
||||
```
|
||||
|
||||
@ -443,7 +425,7 @@ the name of the **table** from the child is used.
|
||||
Note that original model is not only not used, the table for this model is removed from metadata:
|
||||
|
||||
```python
|
||||
Bus2.Meta.metadata.tables.keys()
|
||||
Bus2.ormar_config.metadata.tables.keys()
|
||||
dict_keys(['test_date_models', 'categories', 'subjects', 'persons', 'trucks', 'buses',
|
||||
'cars_x_persons_trucks2', 'trucks2', 'cars_x_persons_buses2', 'buses2'])
|
||||
```
|
||||
@ -469,26 +451,24 @@ Ormar allows you to skip certain fields in inherited model that are coming from
|
||||
!!!Note
|
||||
Note that the same behaviour can be achieved by splitting the model into more abstract models and mixins - which is a preferred way in normal circumstances.
|
||||
|
||||
To skip certain fields from a child model, list all fields that you want to skip in `model.Meta.exclude_parent_fields` parameter like follows:
|
||||
To skip certain fields from a child model, list all fields that you want to skip in `model.ormar_config.exclude_parent_fields` parameter like follows:
|
||||
|
||||
```python
|
||||
metadata = sa.MetaData()
|
||||
db = databases.Database(DATABASE_URL)
|
||||
base_ormar_config = OrmarConfig(
|
||||
metadata=sa.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
class AuditModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_by: str = ormar.String(max_length=100)
|
||||
updated_by: str = ormar.String(max_length=100, default="Sam")
|
||||
|
||||
|
||||
class DateFieldsModel(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_date: datetime.datetime = ormar.DateTime(
|
||||
default=datetime.datetime.now, name="creation_date"
|
||||
@ -499,10 +479,11 @@ class DateFieldsModel(ormar.Model):
|
||||
|
||||
|
||||
class Category(DateFieldsModel, AuditModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="categories",
|
||||
# set fields that should be skipped
|
||||
exclude_parent_fields = ["updated_by", "updated_date"]
|
||||
exclude_parent_fields=["updated_by", "updated_date"],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
@ -523,38 +504,32 @@ Note how you simply need to provide field names and it will exclude the parent f
|
||||
The same effect can be achieved by splitting base classes like:
|
||||
|
||||
```python
|
||||
metadata = sa.MetaData()
|
||||
db = databases.Database(DATABASE_URL)
|
||||
base_ormar_config = OrmarConfig(
|
||||
metadata=sa.MetaData(),
|
||||
database=databases.Database(DATABASE_URL),
|
||||
)
|
||||
|
||||
|
||||
class AuditCreateModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_by: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class AuditUpdateModel(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
updated_by: str = ormar.String(max_length=100, default="Sam")
|
||||
|
||||
class CreateDateFieldsModel(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
created_date: datetime.datetime = ormar.DateTime(
|
||||
default=datetime.datetime.now, name="creation_date"
|
||||
)
|
||||
|
||||
class UpdateDateFieldsModel(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
abstract = True
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(abstract=True)
|
||||
|
||||
updated_date: datetime.datetime = ormar.DateTime(
|
||||
default=datetime.datetime.now, name="modification_date"
|
||||
@ -562,8 +537,7 @@ class UpdateDateFieldsModel(ormar.Model):
|
||||
|
||||
|
||||
class Category(CreateDateFieldsModel, AuditCreateModel):
|
||||
class Meta(ormar.ModelMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||
|
||||
@ -20,27 +20,27 @@ For example to list pydantic model fields you can:
|
||||
|
||||
## Sqlalchemy Table
|
||||
|
||||
To access auto created sqlalchemy table you can use `Model.Meta.table` parameter
|
||||
To access auto created sqlalchemy table you can use `Model.ormar_config.table` parameter
|
||||
|
||||
For example to list table columns you can:
|
||||
|
||||
```Python hl_lines="20"
|
||||
```Python hl_lines="24"
|
||||
--8<-- "../docs_src/models/docs004.py"
|
||||
```
|
||||
|
||||
!!!tip
|
||||
You can access table primary key name by `Course.Meta.pkname`
|
||||
You can access table primary key name by `Course.ormar_config.pkname`
|
||||
|
||||
!!!info
|
||||
For more options visit official [sqlalchemy-metadata][sqlalchemy-metadata] documentation.
|
||||
|
||||
## Fields Definition
|
||||
|
||||
To access ormar `Fields` you can use `Model.Meta.model_fields` parameter
|
||||
To access ormar `Fields` you can use `Model.ormar_config.model_fields` parameter
|
||||
|
||||
For example to list table model fields you can:
|
||||
|
||||
```Python hl_lines="20"
|
||||
```Python hl_lines="22"
|
||||
--8<-- "../docs_src/models/docs005.py"
|
||||
```
|
||||
|
||||
|
||||
@ -13,37 +13,37 @@ Available methods are described below.
|
||||
## `pydantic` methods
|
||||
|
||||
Note that each `ormar.Model` is also a `pydantic.BaseModel`, so all `pydantic` methods are also available on a model,
|
||||
especially `dict()` and `json()` methods that can also accept `exclude`, `include` and other parameters.
|
||||
especially `model_dump()` and `model_dump_json()` methods that can also accept `exclude`, `include` and other parameters.
|
||||
|
||||
To read more check [pydantic][pydantic] documentation
|
||||
|
||||
## construct
|
||||
## model_construct()
|
||||
|
||||
`construct` is a raw equivalent of `__init__` method used for construction of new instances.
|
||||
`model_construct` is a raw equivalent of `__init__` method used for construction of new instances.
|
||||
|
||||
The difference is that `construct` skips validations, so it should be used when you know that data is correct and can be trusted.
|
||||
The difference is that `model_construct` skips validations, so it should be used when you know that data is correct and can be trusted.
|
||||
The benefit of using construct is the speed of execution due to skipped validation.
|
||||
|
||||
!!!note
|
||||
Note that in contrast to `pydantic.construct` method - the `ormar` equivalent will also process the nested related models.
|
||||
Note that in contrast to `pydantic.model_construct` method - the `ormar` equivalent will also process the nested related models.
|
||||
|
||||
!!!warning
|
||||
Bear in mind that due to skipped validation the `construct` method does not perform any conversions, checks etc.
|
||||
So it's your responsibility to provide that data that is valid and can be consumed by the database.
|
||||
Bear in mind that due to skipped validation the `construct` method does not perform any conversions, checks etc.
|
||||
So it's your responsibility to provide that data that is valid and can be consumed by the database.
|
||||
|
||||
The only two things that construct still performs are:
|
||||
The only two things that construct still performs are:
|
||||
|
||||
* Providing a `default` value for not set fields
|
||||
* Initialize nested ormar models if you pass a dictionary or a primary key value
|
||||
* Providing a `default` value for not set fields
|
||||
* Initialize nested ormar models if you pass a dictionary or a primary key value
|
||||
|
||||
## dict
|
||||
## model_dump()
|
||||
|
||||
`dict` is a method inherited from `pydantic`, yet `ormar` adds its own parameters and has some nuances when working with default values,
|
||||
`model_dump` is a method inherited from `pydantic`, yet `ormar` adds its own parameters and has some nuances when working with default values,
|
||||
therefore it's listed here for clarity.
|
||||
|
||||
`dict` as the name suggests export data from model tree to dictionary.
|
||||
`model_dump` as the name suggests export data from model tree to dictionary.
|
||||
|
||||
Explanation of dict parameters:
|
||||
Explanation of model_dump parameters:
|
||||
|
||||
### include (`ormar` modified)
|
||||
|
||||
@ -55,14 +55,14 @@ Note that `pydantic` has an uncommon pattern of including/ excluding fields in l
|
||||
And if you want to exclude the field in all children you need to pass a `__all__` key to dictionary.
|
||||
|
||||
You cannot exclude nested models in `Set`s in `pydantic` but you can in `ormar`
|
||||
(by adding double underscore on relation name i.e. to exclude name of category for a book you cen use `exclude={"book__category__name"}`)
|
||||
(by adding double underscore on relation name i.e. to exclude name of category for a book you can use `exclude={"book__category__name"}`)
|
||||
|
||||
`ormar` does not support by index exclusion/ inclusions and accepts a simplified and more user-friendly notation.
|
||||
|
||||
To check how you can include/exclude fields, including nested fields check out [fields](../queries/select-columns.md#fields) section that has an explanation and a lot of samples.
|
||||
|
||||
!!!note
|
||||
The fact that in `ormar` you can exclude nested models in sets, you can exclude from a whole model tree in `response_model_exclude` and `response_model_include` in fastapi!
|
||||
The fact that in `ormar` you can exclude nested models in sets, you can exclude from a whole model tree in `response_model_exclude` and `response_model_include` in fastapi!
|
||||
|
||||
### exclude (`ormar` modified)
|
||||
|
||||
@ -81,7 +81,7 @@ You cannot exclude nested models in `Set`s in `pydantic` but you can in `ormar`
|
||||
To check how you can include/exclude fields, including nested fields check out [fields](../queries/select-columns.md#fields) section that has an explanation and a lot of samples.
|
||||
|
||||
!!!note
|
||||
The fact that in `ormar` you can exclude nested models in sets, you can exclude from a whole model tree in `response_model_exclude` and `response_model_include` in fastapi!
|
||||
The fact that in `ormar` you can exclude nested models in sets, you can exclude from a whole model tree in `response_model_exclude` and `response_model_include` in fastapi!
|
||||
|
||||
### exclude_unset
|
||||
|
||||
@ -90,16 +90,13 @@ To check how you can include/exclude fields, including nested fields check out [
|
||||
Flag indicates whether fields which were not explicitly set when creating the model should be excluded from the returned dictionary.
|
||||
|
||||
!!!warning
|
||||
Note that after you save data into database each field has its own value -> either provided by you, default, or `None`.
|
||||
Note that after you save data into database each field has its own value -> either provided by you, default, or `None`.
|
||||
|
||||
That means that when you load the data from database, **all** fields are set, and this flag basically stop working!
|
||||
That means that when you load the data from database, **all** fields are set, and this flag basically stop working!
|
||||
|
||||
```python
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="Test")
|
||||
@ -107,10 +104,7 @@ class Category(ormar.Model):
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -118,17 +112,17 @@ class Item(ormar.Model):
|
||||
categories: List[Category] = ormar.ManyToMany(Category)
|
||||
|
||||
category = Category(name="Test 2")
|
||||
assert category.dict() == {'id': None, 'items': [], 'name': 'Test 2',
|
||||
assert category.model_dump() == {'id': None, 'items': [], 'name': 'Test 2',
|
||||
'visibility': True}
|
||||
assert category.dict(exclude_unset=True) == {'items': [], 'name': 'Test 2'}
|
||||
assert category.model_dump(exclude_unset=True) == {'items': [], 'name': 'Test 2'}
|
||||
|
||||
await category.save()
|
||||
category2 = await Category.objects.get()
|
||||
assert category2.dict() == {'id': 1, 'items': [], 'name': 'Test 2',
|
||||
assert category2.model_dump() == {'id': 1, 'items': [], 'name': 'Test 2',
|
||||
'visibility': True}
|
||||
# NOTE how after loading from db all fields are set explicitly
|
||||
# as this is what happens when you populate a model from db
|
||||
assert category2.dict(exclude_unset=True) == {'id': 1, 'items': [],
|
||||
assert category2.model_dump(exclude_unset=True) == {'id': 1, 'items': [],
|
||||
'name': 'Test 2', 'visibility': True}
|
||||
```
|
||||
|
||||
@ -140,20 +134,14 @@ Flag indicates are equal to their default values (whether set or otherwise) shou
|
||||
|
||||
```python
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="Test")
|
||||
visibility: bool = ormar.Boolean(default=True)
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -162,15 +150,15 @@ class Item(ormar.Model):
|
||||
|
||||
category = Category()
|
||||
# note that Integer pk is by default autoincrement so optional
|
||||
assert category.dict() == {'id': None, 'items': [], 'name': 'Test', 'visibility': True}
|
||||
assert category.dict(exclude_defaults=True) == {'items': []}
|
||||
assert category.model_dump() == {'id': None, 'items': [], 'name': 'Test', 'visibility': True}
|
||||
assert category.model_dump(exclude_defaults=True) == {'items': []}
|
||||
|
||||
# save and reload the data
|
||||
await category.save()
|
||||
category2 = await Category.objects.get()
|
||||
|
||||
assert category2.dict() == {'id': 1, 'items': [], 'name': 'Test', 'visibility': True}
|
||||
assert category2.dict(exclude_defaults=True) == {'id': 1, 'items': []}
|
||||
assert category2.model_dump() == {'id': 1, 'items': [], 'name': 'Test', 'visibility': True}
|
||||
assert category2.model_dump(exclude_defaults=True) == {'id': 1, 'items': []}
|
||||
```
|
||||
|
||||
### exclude_none
|
||||
@ -181,10 +169,7 @@ Flag indicates whether fields which are equal to `None` should be excluded from
|
||||
|
||||
```python
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="Test", nullable=True)
|
||||
@ -192,10 +177,7 @@ class Category(ormar.Model):
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -204,16 +186,16 @@ class Item(ormar.Model):
|
||||
|
||||
|
||||
category = Category(name=None)
|
||||
assert category.dict() == {'id': None, 'items': [], 'name': None,
|
||||
assert category.model_dump() == {'id': None, 'items': [], 'name': None,
|
||||
'visibility': True}
|
||||
# note the id is not set yet so None and excluded
|
||||
assert category.dict(exclude_none=True) == {'items': [], 'visibility': True}
|
||||
assert category.model_dump(exclude_none=True) == {'items': [], 'visibility': True}
|
||||
|
||||
await category.save()
|
||||
category2 = await Category.objects.get()
|
||||
assert category2.dict() == {'id': 1, 'items': [], 'name': None,
|
||||
assert category2.model_dump() == {'id': 1, 'items': [], 'name': None,
|
||||
'visibility': True}
|
||||
assert category2.dict(exclude_none=True) == {'id': 1, 'items': [],
|
||||
assert category2.model_dump(exclude_none=True) == {'id': 1, 'items': [],
|
||||
'visibility': True}
|
||||
|
||||
```
|
||||
@ -226,17 +208,14 @@ Setting flag to `True` will exclude all primary key columns in a tree, including
|
||||
|
||||
```python
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
item1 = Item(id=1, name="Test Item")
|
||||
assert item1.dict() == {"id": 1, "name": "Test Item"}
|
||||
assert item1.dict(exclude_primary_keys=True) == {"name": "Test Item"}
|
||||
assert item1.model_dump() == {"id": 1, "name": "Test Item"}
|
||||
assert item1.model_dump(exclude_primary_keys=True) == {"name": "Test Item"}
|
||||
```
|
||||
|
||||
### exclude_through_models (`ormar` only)
|
||||
@ -249,20 +228,14 @@ Setting the `exclude_through_models=True` will exclude all through models, inclu
|
||||
|
||||
```python
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -280,7 +253,7 @@ await Item(**item_dict).save_related(follow=True, save_all=True)
|
||||
item = await Item.objects.select_related("categories").get()
|
||||
|
||||
# by default you can see the through models (itemcategory)
|
||||
assert item.dict() == {'id': 1, 'name': 'test',
|
||||
assert item.model_dump() == {'id': 1, 'name': 'test',
|
||||
'categories': [
|
||||
{'id': 1, 'name': 'test cat',
|
||||
'itemcategory': {'id': 1, 'category': None, 'item': None}},
|
||||
@ -289,7 +262,7 @@ assert item.dict() == {'id': 1, 'name': 'test',
|
||||
]}
|
||||
|
||||
# you can exclude those fields/ models
|
||||
assert item.dict(exclude_through_models=True) == {
|
||||
assert item.model_dump(exclude_through_models=True) == {
|
||||
'id': 1, 'name': 'test',
|
||||
'categories': [
|
||||
{'id': 1, 'name': 'test cat'},
|
||||
@ -297,49 +270,45 @@ assert item.dict(exclude_through_models=True) == {
|
||||
]}
|
||||
```
|
||||
|
||||
## json
|
||||
## model_dump_json()
|
||||
|
||||
`json()` has exactly the same parameters as `dict()` so check above.
|
||||
`model_dump_json()` has exactly the same parameters as `model_dump()` so check above.
|
||||
|
||||
Of course the end result is a string with json representation and not a dictionary.
|
||||
|
||||
## get_pydantic
|
||||
## get_pydantic()
|
||||
|
||||
`get_pydantic(include: Union[Set, Dict] = None, exclude: Union[Set, Dict] = None)`
|
||||
|
||||
This method allows you to generate `pydantic` models from your ormar models without you needing to retype all the fields.
|
||||
|
||||
Note that if you have nested models, it **will generate whole tree of pydantic models for you!**
|
||||
Note that if you have nested models, it **will generate whole tree of pydantic models for you!** but in a way that prevents cyclic references issues.
|
||||
|
||||
Moreover, you can pass `exclude` and/or `include` parameters to keep only the fields that you want to, including in nested models.
|
||||
|
||||
That means that this way you can effortlessly create pydantic models for requests and responses in `fastapi`.
|
||||
|
||||
!!!Note
|
||||
To read more about possible excludes/includes and how to structure your exclude dictionary or set visit [fields](../queries/select-columns.md#fields) section of documentation
|
||||
To read more about possible excludes/includes and how to structure your exclude dictionary or set visit [fields](../queries/select-columns.md#fields) section of documentation
|
||||
|
||||
Given sample ormar models like follows:
|
||||
|
||||
```python
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database=databases.Database(DATABASE_URL, force_rollback=True),
|
||||
)
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, default="test")
|
||||
@ -361,8 +330,8 @@ class Category(BaseModel):
|
||||
```
|
||||
|
||||
!!!warning
|
||||
Note that it's not a good practice to have several classes with same name in one module, as well as it would break `fastapi` docs.
|
||||
Thats's why ormar adds random 3 uppercase letters to the class name. In example above it means that in reality class would be named i.e. `Category_XIP(BaseModel)`.
|
||||
Note that it's not a good practice to have several classes with same name in one module, as well as it would break `fastapi` docs.
|
||||
Thats's why ormar adds random 3 uppercase letters to the class name. In example above it means that in reality class would be named i.e. `Category_XIP(BaseModel)`.
|
||||
|
||||
To exclude or include nested fields you can use dict or double underscores.
|
||||
|
||||
@ -382,19 +351,19 @@ class Category(BaseModel):
|
||||
items: Optional[List[Item]]
|
||||
```
|
||||
|
||||
Of course, you can use also deeply nested structures and ormar will generate it pydantic equivalent you (in a way that exclude loops).
|
||||
Of course, you can use also deeply nested structures and ormar will generate it's pydantic equivalent for you (in a way that exclude loops).
|
||||
|
||||
Note how `Item` model above does not have a reference to `Category` although in ormar the relation is bidirectional (and `ormar.Item` has `categories` field).
|
||||
|
||||
!!!warning
|
||||
Note that the generated pydantic model will inherit all **field** validators from the original `ormar` model, that includes the ormar choices validator as well as validators defined with `pydantic.validator` decorator.
|
||||
Note that the generated pydantic model will inherit all **field** validators from the original `ormar` model, that includes the ormar choices validator as well as validators defined with `pydantic.validator` decorator.
|
||||
|
||||
But, at the same time all root validators present on `ormar` models will **NOT** be copied to the generated pydantic model. Since root validator can operate on all fields and a user can exclude some fields during generation of pydantic model it's not safe to copy those validators.
|
||||
If required, you need to redefine/ manually copy them to generated pydantic model.
|
||||
But, at the same time all root validators present on `ormar` models will **NOT** be copied to the generated pydantic model. Since root validator can operate on all fields and a user can exclude some fields during generation of pydantic model it's not safe to copy those validators.
|
||||
If required, you need to redefine/ manually copy them to generated pydantic model.
|
||||
|
||||
## load
|
||||
## load()
|
||||
|
||||
By default when you query a table without prefetching related models, the ormar will still construct
|
||||
By default, when you query a table without prefetching related models, the ormar will still construct
|
||||
your related models, but populate them only with the pk value. You can load the related model by calling `load()` method.
|
||||
|
||||
`load()` can also be used to refresh the model from the database (if it was changed by some other process).
|
||||
@ -409,14 +378,14 @@ await track.album.load()
|
||||
track.album.name # will return 'Malibu'
|
||||
```
|
||||
|
||||
## load_all
|
||||
## load_all()
|
||||
|
||||
`load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None) -> Model`
|
||||
|
||||
Method works like `load()` but also goes through all relations of the `Model` on which the method is called,
|
||||
and reloads them from database.
|
||||
|
||||
By default the `load_all` method loads only models that are directly related (one step away) to the model on which the method is called.
|
||||
By default, the `load_all` method loads only models that are directly related (one step away) to the model on which the method is called.
|
||||
|
||||
But you can specify the `follow=True` parameter to traverse through nested models and load all of them in the relation tree.
|
||||
|
||||
@ -442,7 +411,7 @@ Method performs one database query so it's more efficient than nested calls to `
|
||||
!!!warning
|
||||
All relations are cleared on `load_all()`, so if you exclude some nested models they will be empty after call.
|
||||
|
||||
## save
|
||||
## save()
|
||||
|
||||
`save() -> self`
|
||||
|
||||
@ -461,7 +430,7 @@ track = await Track.objects.get(name='The Bird')
|
||||
await track.save() # will raise integrity error as pk is populated
|
||||
```
|
||||
|
||||
## update
|
||||
## update()
|
||||
|
||||
`update(_columns: List[str] = None, **kwargs) -> self`
|
||||
|
||||
@ -480,12 +449,9 @@ To update only selected columns from model into the database provide a list of c
|
||||
|
||||
In example:
|
||||
|
||||
```python
|
||||
```python hl_lines="16"
|
||||
class Movie(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "movies"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="title")
|
||||
@ -510,9 +476,9 @@ assert terminator.year == 1984
|
||||
```
|
||||
|
||||
!!!warning
|
||||
Note that `update()` does not refresh the instance of the Model, so if you change more columns than you pass in `_columns` list your Model instance will have different values than the database!
|
||||
Note that `update()` does not refresh the instance of the Model, so if you change more columns than you pass in `_columns` list your Model instance will have different values than the database!
|
||||
|
||||
## upsert
|
||||
## upsert()
|
||||
|
||||
`upsert(**kwargs) -> self`
|
||||
|
||||
@ -531,7 +497,7 @@ await track.upsert(name='The Bird Strikes Again') # will call update as pk is al
|
||||
```
|
||||
|
||||
|
||||
## delete
|
||||
## delete()
|
||||
|
||||
You can delete models by using `QuerySet.delete()` method or by using your model and calling `delete()` method.
|
||||
|
||||
@ -543,7 +509,7 @@ await track.delete() # will delete the model from database
|
||||
!!!tip
|
||||
Note that that `track` object stays the same, only record in the database is removed.
|
||||
|
||||
## save_related
|
||||
## save_related()
|
||||
|
||||
`save_related(follow: bool = False, save_all: bool = False, exclude=Optional[Union[Set, Dict]]) -> None`
|
||||
|
||||
@ -566,11 +532,11 @@ If you want to skip saving some of the relations you can pass `exclude` paramete
|
||||
or it can be a dictionary that can also contain nested items.
|
||||
|
||||
!!!note
|
||||
Note that `exclude` parameter in `save_related` accepts only relation fields names, so
|
||||
if you pass any other fields they will be saved anyway
|
||||
Note that `exclude` parameter in `save_related` accepts only relation fields names, so
|
||||
if you pass any other fields they will be saved anyway
|
||||
|
||||
!!!note
|
||||
To read more about the structure of possible values passed to `exclude` check `Queryset.fields` method documentation.
|
||||
To read more about the structure of possible values passed to `exclude` check `Queryset.fields` method documentation.
|
||||
|
||||
!!!warning
|
||||
To avoid circular updates with `follow=True` set, `save_related` keeps a set of already visited Models on each branch of relation tree,
|
||||
@ -584,18 +550,14 @@ Example:
|
||||
|
||||
```python
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
department_name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
course_name: str = ormar.String(max_length=100)
|
||||
@ -604,9 +566,7 @@ class Course(ormar.Model):
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -647,7 +607,7 @@ to_exclude = {
|
||||
}
|
||||
# after excluding ids and through models you get exact same payload used to
|
||||
# construct whole tree
|
||||
assert department_check.dict(exclude=to_exclude) == to_save
|
||||
assert department_check.model_dump(exclude=to_exclude) == to_save
|
||||
|
||||
```
|
||||
|
||||
|
||||
@ -16,14 +16,14 @@ engine = sqlalchemy.create_engine("sqlite:///test.db")
|
||||
metadata.create_all(engine)
|
||||
```
|
||||
|
||||
You can also create single tables, sqlalchemy tables are exposed in `ormar.Meta` class.
|
||||
You can also create single tables, sqlalchemy tables are exposed in `ormar.ormar_config` object.
|
||||
|
||||
```python
|
||||
import sqlalchemy
|
||||
# get your database url in sqlalchemy format - same as used with databases instance used in Model definition
|
||||
engine = sqlalchemy.create_engine("sqlite:///test.db")
|
||||
# Artist is an ormar model from previous examples
|
||||
Artist.Meta.table.create(engine)
|
||||
Artist.ormar_config.table.create(engine)
|
||||
```
|
||||
|
||||
!!!warning
|
||||
|
||||
10
docs/mypy.md
10
docs/mypy.md
@ -1,14 +1,6 @@
|
||||
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.
|
||||
Please use notation introduced in version 0.4.0.
|
||||
|
||||
```Python hl_lines="15-17"
|
||||
--8<-- "../docs_src/models/docs012.py"
|
||||
|
||||
@ -32,10 +32,11 @@ the count will be the total number of rows returned
|
||||
|
||||
```python
|
||||
class Book(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "books"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
tablename="book"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
@ -60,10 +61,11 @@ Returns a bool value to confirm if there are rows matching the given criteria (a
|
||||
|
||||
```python
|
||||
class Book(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "books"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
tablename="book"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
|
||||
@ -30,10 +30,12 @@ The allowed kwargs are `Model` fields names and proper value types.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="album"
|
||||
)
|
||||
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -52,7 +54,7 @@ await malibu.save()
|
||||
```
|
||||
|
||||
!!!tip
|
||||
Check other `Model` methods in [models][models]
|
||||
Check other `Model` methods in [models][models]
|
||||
|
||||
## get_or_create
|
||||
|
||||
@ -68,10 +70,11 @@ i.e. `get_or_create(_defaults: {"title": "I win"}, title="never used")` will alw
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="album"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -106,7 +109,7 @@ assert album == album2
|
||||
|
||||
Updates the model, or in case there is no match in database creates a new one.
|
||||
|
||||
```Python hl_lines="26-32"
|
||||
```Python hl_lines="40-48"
|
||||
--8<-- "../docs_src/queries/docs003.py"
|
||||
```
|
||||
|
||||
@ -122,7 +125,7 @@ Allows you to create multiple objects at once.
|
||||
|
||||
A valid list of `Model` objects needs to be passed.
|
||||
|
||||
```python hl_lines="21-27"
|
||||
```python hl_lines="26-32"
|
||||
--8<-- "../docs_src/queries/docs004.py"
|
||||
```
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai
|
||||
|
||||
Return number of rows deleted.
|
||||
|
||||
```python hl_lines="26-30"
|
||||
```python hl_lines="40-44"
|
||||
--8<-- "../docs_src/queries/docs005.py"
|
||||
```
|
||||
|
||||
@ -59,20 +59,14 @@ If you specify the keep_reversed flag to `False` `ormar` will also delete the re
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -104,20 +98,14 @@ If you specify the keep_reversed flag to `False` `ormar` will also delete the re
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
|
||||
@ -35,20 +35,14 @@ a filter across an FK relationship.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -197,20 +191,14 @@ conditions.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -245,26 +233,21 @@ Since it sounds more complicated than it is, let's look at some examples.
|
||||
|
||||
Given a sample models like this:
|
||||
```python
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
@ -721,7 +704,7 @@ Ordering in sql will be applied in order of names you provide in order_by.
|
||||
Given sample Models like following:
|
||||
|
||||
```python
|
||||
--8 < -- "../../docs_src/queries/docs007.py"
|
||||
--8<-- "../docs_src/queries/docs007.py"
|
||||
```
|
||||
|
||||
To order by main model field just provide a field name
|
||||
@ -808,7 +791,7 @@ Since order of rows in a database is not guaranteed, `ormar` **always** issues a
|
||||
|
||||
When querying the database with given model by default the `Model` is ordered by the `primary_key`
|
||||
column ascending. If you wish to change the default behaviour you can do it by providing `orders_by`
|
||||
parameter to model `Meta` class.
|
||||
parameter to `ormar_config`.
|
||||
|
||||
!!!tip
|
||||
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation
|
||||
@ -823,8 +806,8 @@ Order in which order_by clauses are applied is as follows:
|
||||
|
||||
* Explicitly passed `order_by()` calls in query
|
||||
* Relation passed `orders_by` and `related_orders_by` if exists
|
||||
* Model `Meta` class `orders_by`
|
||||
* Model `primary_key` column ascending (fallback, used if none of above provided)
|
||||
* Model's `ormar_config` object `orders_by`
|
||||
* Model's `primary_key` column ascending (fallback, used if none of above provided)
|
||||
|
||||
**Order from only one source is applied to each `Model` (so that you can always overwrite it in a single query).**
|
||||
|
||||
|
||||
@ -46,20 +46,14 @@ To chain related `Models` relation use double underscores between names.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -82,10 +76,7 @@ You can provide a string or a list of strings (or a field/ list of fields)
|
||||
|
||||
```python
|
||||
class SchoolClass(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "schoolclasses"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="schoolclasses")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -93,20 +84,14 @@ class SchoolClass(ormar.Model):
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "students"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -115,10 +100,7 @@ class Student(ormar.Model):
|
||||
|
||||
|
||||
class Teacher(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "teachers"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -182,18 +164,14 @@ If `follow=True` is set it adds also related models of related models.
|
||||
With sample date like follow:
|
||||
|
||||
```python
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
base_ormar_config = OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL, force_rollback=True),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Address(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "addresses"
|
||||
ormar_config = base_ormar_config.copy(tablename="addresses")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
street: str = ormar.String(max_length=100, nullable=False)
|
||||
@ -203,8 +181,7 @@ class Address(ormar.Model):
|
||||
|
||||
|
||||
class Branch(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "branches"
|
||||
ormar_config = base_ormar_config.copy(tablename="branches")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False)
|
||||
@ -212,8 +189,7 @@ class Branch(ormar.Model):
|
||||
|
||||
|
||||
class Company(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "companies"
|
||||
ormar_config = base_ormar_config.copy(tablename="companies")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100, nullable=False, name="company_name")
|
||||
@ -264,20 +240,14 @@ To chain related `Models` relation use double underscores between names.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "tracks"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -301,10 +271,7 @@ You can provide a string, or a list of strings
|
||||
|
||||
```python
|
||||
class SchoolClass(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "schoolclasses"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="schoolclasses")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -312,20 +279,14 @@ class SchoolClass(ormar.Model):
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "students"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -334,10 +295,7 @@ class Student(ormar.Model):
|
||||
|
||||
|
||||
class Teacher(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "teachers"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -432,12 +390,12 @@ might be faster despite it needs to perform three separate queries instead of on
|
||||
|
||||
#### Memory
|
||||
|
||||
`ormar` is a mini ORM meaning that it does not keep a registry of already loaded models.
|
||||
`ormar` is does not keep a registry of already loaded models.
|
||||
|
||||
That means that in `select_related` example above you will always have 10 000 Models A,
|
||||
30 000 Models B
|
||||
(even if the unique number of rows in db is 3 - processing of `select_related` spawns **
|
||||
new** child models for each parent model). And 60 000 Models C.
|
||||
(even if the unique number of rows in db is 3 - processing of `select_related` spawns
|
||||
**new** child models for each parent model). And 60 000 Models C.
|
||||
|
||||
If the same Model B is shared by rows 1, 10, 100 etc. and you update one of those, the
|
||||
rest of rows that share the same child will **not** be updated on the spot. If you
|
||||
@ -471,7 +429,7 @@ that `select_related` will use more memory as each child is instantiated as a ne
|
||||
|
||||
```python
|
||||
# will return False (note that id is a python `builtin` function not ormar one).
|
||||
id(row1.child1) == (ro100.child1)
|
||||
id(row1.child1) == id(ro100.child1)
|
||||
|
||||
# from above - will also return False
|
||||
id(model1) == id(model2)
|
||||
|
||||
@ -22,10 +22,11 @@ Combines the `offset` and `limit` methods based on page number and size
|
||||
|
||||
```python
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "track"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
tablename="track"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -52,10 +53,11 @@ use the `limit_raw_sql` parameter flag, and set it to `True`.
|
||||
|
||||
```python
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "track"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
tablename="track"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -86,10 +88,11 @@ use the `limit_raw_sql` parameter flag, and set it to `True`.
|
||||
|
||||
```python
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "track"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
tablename="track"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
|
||||
@ -40,8 +40,7 @@ Example:
|
||||
# declared models
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
@ -49,8 +48,7 @@ class Category(ormar.Model):
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "posts"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=200)
|
||||
@ -83,16 +81,14 @@ Note how nested models columns will be prefixed with full relation path coming f
|
||||
# declare models
|
||||
|
||||
class User(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Role(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -216,8 +212,7 @@ Example:
|
||||
# declared models
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
@ -225,8 +220,7 @@ class Category(ormar.Model):
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "posts"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=200)
|
||||
@ -257,8 +251,7 @@ Let's complicate the relation and modify the previously mentioned Category model
|
||||
|
||||
```python
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
|
||||
@ -31,10 +31,11 @@ Passing a criteria is actually calling filter(*args, **kwargs) method described
|
||||
|
||||
```python
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "track"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="track"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -74,10 +75,7 @@ a new one with given kwargs and _defaults.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="album")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -114,10 +112,7 @@ Gets the first row from the db ordered by primary key column ascending.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="album")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -143,20 +138,14 @@ If there are no rows meeting the criteria an empty list is returned.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="album")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "track"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="track")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
@ -186,10 +175,7 @@ If there are no rows meeting the criteria an empty async generator is returned.
|
||||
|
||||
```python
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="album")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -24,52 +24,7 @@ With `fields()` you can select subset of model columns to limit the data load.
|
||||
Given a sample data like following:
|
||||
|
||||
```python
|
||||
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')
|
||||
|
||||
|
||||
--8<-- "../docs_src/select_columns/docs001.py"
|
||||
```
|
||||
|
||||
You can select specified fields by passing a `str, List[str], Set[str] or dict` with
|
||||
@ -78,8 +33,13 @@ nested definition.
|
||||
To include related models use
|
||||
notation `{related_name}__{column}[__{optional_next} etc.]`.
|
||||
|
||||
```python hl_lines="1"
|
||||
all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all()
|
||||
```python hl_lines="1-6"
|
||||
all_cars = await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields(['id', 'name', 'manufacturer__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'])
|
||||
@ -97,9 +57,14 @@ for those models in fields
|
||||
|
||||
- implies a list of all fields for those nested models.
|
||||
|
||||
```python hl_lines="1"
|
||||
all_cars = await Car.objects.select_related('manufacturer').fields('id').fields(
|
||||
['name']).all()
|
||||
```python hl_lines="1-7"
|
||||
all_cars = await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields('id')
|
||||
.fields(['name'])
|
||||
.all()
|
||||
)
|
||||
# all fields from company model are selected
|
||||
assert all_cars[0].manufacturer.name == 'Toyota'
|
||||
assert all_cars[0].manufacturer.founded == 1937
|
||||
@ -115,8 +80,12 @@ assert all_cars[0].manufacturer.founded == 1937
|
||||
You cannot exclude mandatory model columns - `manufacturer__name` in this example.
|
||||
|
||||
```python
|
||||
await Car.objects.select_related('manufacturer').fields(
|
||||
['id', 'name', 'manufacturer__founded']).all()
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields(['id', 'name', 'manufacturer__founded'])
|
||||
.all()
|
||||
)
|
||||
# will raise pydantic ValidationError as company.name is required
|
||||
```
|
||||
|
||||
@ -138,38 +107,71 @@ Below you can see examples that are equivalent:
|
||||
|
||||
```python
|
||||
# 1. like in example above
|
||||
await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all()
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields(['id', 'name', 'manufacturer__name'])
|
||||
.all()
|
||||
)
|
||||
|
||||
# 2. to mark a field as required use ellipsis
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {
|
||||
'name': ...}
|
||||
}).all()
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {
|
||||
'name': ...
|
||||
}
|
||||
})
|
||||
.all()
|
||||
)
|
||||
|
||||
# 3. to include whole nested model use ellipsis
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': ...
|
||||
}).all()
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': ...
|
||||
})
|
||||
.all()
|
||||
)
|
||||
|
||||
# 4. to specify fields at last nesting level you can also use set - equivalent to 2. above
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'name'}
|
||||
}).all()
|
||||
# 4. to specify fields at last nesting level
|
||||
# you can also use set - equivalent to 2. above
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'name'}
|
||||
})
|
||||
.all()
|
||||
)
|
||||
|
||||
# 5. of course set can have multiple fields
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'name', 'founded'}
|
||||
}).all()
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'name', 'founded'}
|
||||
})
|
||||
.all()
|
||||
)
|
||||
|
||||
# 6. you can include all nested fields but it will be equivalent of 3. above which is shorter
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'id', 'name', 'founded'}
|
||||
}).all()
|
||||
# 6. you can include all nested fields,
|
||||
# but it will be equivalent of 3. above which is shorter
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'id', 'name', 'founded'}
|
||||
})
|
||||
.all()
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
@ -201,74 +203,65 @@ exclude fields from whole hierarchy.
|
||||
|
||||
Below you can find few simple examples:
|
||||
|
||||
```python hl_lines="47 48 60 61 67"
|
||||
import databases
|
||||
import sqlalchemy
|
||||
```python
|
||||
--8<-- "../docs_src/select_columns/docs001.py"
|
||||
```
|
||||
|
||||
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').exclude_fields(
|
||||
['year', 'gearbox_type', 'gears', 'aircon_type', 'company__founded']).all()
|
||||
```python
|
||||
# select manufacturer but only name,
|
||||
# to include related models use notation {model_name}__{column}
|
||||
all_cars = await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.exclude_fields([
|
||||
'year',
|
||||
'gearbox_type',
|
||||
'gears',
|
||||
'aircon_type',
|
||||
'company__founded'
|
||||
])
|
||||
.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
|
||||
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
|
||||
# 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').exclude_fields('year').exclude_fields(
|
||||
['gear', 'gearbox_type']).all()
|
||||
# fields() can be called several times,
|
||||
# building up the columns to select
|
||||
# models included in select_related
|
||||
# but with no columns in fields list implies all fields
|
||||
all_cars = await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.exclude_fields('year')
|
||||
.exclude_fields(['gear', 'gearbox_type'])
|
||||
.all()
|
||||
)
|
||||
# all fields 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 - note usage of dict/set this time
|
||||
await Car.objects.select_related('manufacturer').exclude_fields([{'company': {'name'}}]).all()
|
||||
# cannot exclude mandatory model columns,
|
||||
# company__name in this example - note usage of dict/set this time
|
||||
await (
|
||||
Car.objects
|
||||
.select_related('manufacturer')
|
||||
.exclude_fields([{'company': {'name'}}])
|
||||
.all()
|
||||
)
|
||||
# will raise pydantic ValidationError as company.name is required
|
||||
|
||||
```
|
||||
|
||||
@ -29,7 +29,7 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai
|
||||
|
||||
Return number of rows updated.
|
||||
|
||||
```Python hl_lines="26-28"
|
||||
```Python hl_lines="42-44"
|
||||
--8<-- "../docs_src/queries/docs002.py"
|
||||
```
|
||||
|
||||
@ -44,7 +44,7 @@ Return number of rows updated.
|
||||
|
||||
Updates the model, or in case there is no match in database creates a new one.
|
||||
|
||||
```Python hl_lines="26-32"
|
||||
```Python hl_lines="40-48"
|
||||
--8<-- "../docs_src/queries/docs003.py"
|
||||
```
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ Sqlalchemy column and Type are automatically taken from target `Model`.
|
||||
|
||||
To define a relation add `ForeignKey` field that points to related `Model`.
|
||||
|
||||
```Python hl_lines="29"
|
||||
```Python hl_lines="30"
|
||||
--8<-- "../docs_src/fields/docs003.py"
|
||||
```
|
||||
|
||||
@ -24,7 +24,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:
|
||||
|
||||
```Python hl_lines="29 35"
|
||||
```Python hl_lines="29 36"
|
||||
--8<-- "../docs_src/fields/docs001.py"
|
||||
```
|
||||
|
||||
@ -45,15 +45,14 @@ But you cannot:
|
||||
|
||||
* Access the related field from reverse model with `related_name`
|
||||
* Even if you `select_related` from reverse side of the model the returned models won't be populated in reversed instance (the join is not prevented so you still can `filter` and `order_by` over the relation)
|
||||
* The relation won't be populated in `dict()` and `json()`
|
||||
* The relation won't be populated in `model_dump()` and `model_dump_json()`
|
||||
* You cannot pass the nested related objects when populating from dictionary or json (also through `fastapi`). It will be either ignored or error will be raised depending on `extra` setting in pydantic `Config`.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
first_name: str = ormar.String(max_length=80)
|
||||
@ -61,8 +60,7 @@ class Author(ormar.Model):
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
@ -82,8 +80,8 @@ authors = (
|
||||
assert authors[0].first_name == "Test"
|
||||
|
||||
# note that posts are not populated for author even if explicitly
|
||||
# included in select_related - note no posts in dict()
|
||||
assert author.dict(exclude={"id"}) == {"first_name": "Test", "last_name": "Author"}
|
||||
# included in select_related - note no posts in model_dump()
|
||||
assert author.model_dump(exclude={"id"}) == {"first_name": "Test", "last_name": "Author"}
|
||||
|
||||
# still can filter through fields of related model
|
||||
authors = await Author.objects.filter(posts__title="Test Post").all()
|
||||
@ -112,7 +110,7 @@ assert department.courses[0] == course
|
||||
!!!warning
|
||||
If you want to add child model on related model the primary key value for parent model **has to exist in database**.
|
||||
|
||||
Otherwise ormar will raise RelationshipInstanceError as it cannot set child's ForeignKey column value
|
||||
Otherwise ormar will raise `RelationshipInstanceError` as it cannot set child's ForeignKey column value
|
||||
if parent model has no primary key value.
|
||||
|
||||
That means that in example above the department has to be saved before you can call `department.courses.add()`.
|
||||
@ -151,7 +149,7 @@ await department.courses.remove(course, keep_reversed=False)
|
||||
|
||||
Removal of all related models in one call.
|
||||
|
||||
Like remove by default `clear()` nulls the ForeigKey column on child model (all, not matter if they are loaded or not).
|
||||
Like with remove, by default, `clear()` nulls the ForeigKey column on child model (all, not matter if they are loaded or not).
|
||||
|
||||
```python
|
||||
# nulls department column on all courses related to this department
|
||||
@ -173,9 +171,9 @@ To read which methods of QuerySet are available read below [querysetproxy][query
|
||||
|
||||
## related_name
|
||||
|
||||
But you can overwrite this name by providing `related_name` parameter like below:
|
||||
You can overwrite related model field name by providing `related_name` parameter like below:
|
||||
|
||||
```Python hl_lines="29 35"
|
||||
```Python hl_lines="27-29 35"
|
||||
--8<-- "../docs_src/fields/docs002.py"
|
||||
```
|
||||
|
||||
@ -230,7 +228,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.
|
||||
|
||||
```Python hl_lines="34-35"
|
||||
```Python hl_lines="35-36"
|
||||
--8<-- "../docs_src/relations/docs001.py"
|
||||
```
|
||||
|
||||
@ -238,7 +236,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.
|
||||
|
||||
```Python hl_lines="37-38"
|
||||
```Python hl_lines="38-39"
|
||||
--8<-- "../docs_src/relations/docs001.py"
|
||||
```
|
||||
|
||||
@ -246,9 +244,9 @@ You can setup the relation also with just the pk column value of the related mod
|
||||
|
||||
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 `model_dump()` method.
|
||||
|
||||
```Python hl_lines="40-41"
|
||||
```Python hl_lines="41-42"
|
||||
--8<-- "../docs_src/relations/docs001.py"
|
||||
```
|
||||
|
||||
@ -256,7 +254,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).
|
||||
|
||||
```Python hl_lines="43-44"
|
||||
```Python hl_lines="44-45"
|
||||
--8<-- "../docs_src/relations/docs001.py"
|
||||
```
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ To read more about methods, possibilities, definition etc. please read the subse
|
||||
|
||||
To define many-to-one relation use `ForeignKey` field.
|
||||
|
||||
```Python hl_lines="17"
|
||||
```Python hl_lines="26"
|
||||
--8<-- "../docs_src/relations/docs003.py"
|
||||
```
|
||||
|
||||
@ -24,13 +24,11 @@ To define many-to-one relation use `ForeignKey` field.
|
||||
|
||||
The definition of one-to-many relation also uses `ForeignKey`, and it's registered for you automatically.
|
||||
|
||||
So in relation ato example above.
|
||||
So in relation to example above.
|
||||
|
||||
```Python hl_lines="17"
|
||||
```Python hl_lines="7-8"
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -52,21 +50,22 @@ class Department(ormar.Model):
|
||||
|
||||
To define many-to-many relation use `ManyToMany` field.
|
||||
|
||||
```python hl_lines="18"
|
||||
```python hl_lines="19"
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="categories",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "posts"
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
@ -92,18 +91,24 @@ side of the current query for m2m models.
|
||||
So if you query from model `A` to model `B`, only model `B` has through field exposed.
|
||||
Which kind of make sense, since it's a one through model/field for each of related models.
|
||||
|
||||
```python hl_lines="10-15"
|
||||
```python hl_lines="12-21"
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="categories",
|
||||
)
|
||||
|
||||
id = ormar.Integer(primary_key=True)
|
||||
name = ormar.String(max_length=40)
|
||||
|
||||
# you can specify additional fields on through model
|
||||
class PostCategory(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "posts_x_categories"
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="posts_x_categories",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
sort_order: int = ormar.Integer(nullable=True)
|
||||
@ -111,8 +116,10 @@ class PostCategory(ormar.Model):
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
@ -130,7 +137,7 @@ class Post(ormar.Model):
|
||||
|
||||
## Relationship default sort order
|
||||
|
||||
By default relations follow model default sort order so `primary_key` column ascending, or any sort order se in `Meta` class.
|
||||
By default relations follow model default sort order so `primary_key` column ascending, or any sort order se in `ormar_config` object.
|
||||
|
||||
!!!tip
|
||||
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation
|
||||
@ -143,27 +150,26 @@ columns also `Through` model columns `{through_field_name}__{column_name}`
|
||||
|
||||
Sample configuration might look like this:
|
||||
|
||||
```python hl_lines="24"
|
||||
```python hl_lines="23"
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(
|
||||
@ -186,14 +192,12 @@ In order to create auto-relation or create two models that reference each other
|
||||
different relations (remember the reverse side is auto-registered for you), you need to use
|
||||
`ForwardRef` from `typing` module.
|
||||
|
||||
```python hl_lines="1 11 14"
|
||||
```python hl_lines="1 9 12"
|
||||
PersonRef = ForwardRef("Person")
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -9,7 +9,7 @@ Sqlalchemy column and Type are automatically taken from target `Model`.
|
||||
|
||||
## Defining Models
|
||||
|
||||
```Python hl_lines="40"
|
||||
```Python hl_lines="34"
|
||||
--8<-- "../docs_src/relations/docs002.py"
|
||||
```
|
||||
|
||||
@ -24,20 +24,18 @@ news = await Category.objects.create(name="News")
|
||||
|
||||
`ForeignKey` fields are automatically registering reverse side of the relation.
|
||||
|
||||
By default it's child (source) `Model` name + s, like courses in snippet below:
|
||||
By default it's child (source) `Model` name + s, like `posts` in snippet below:
|
||||
|
||||
```python
|
||||
```python hl_lines="25-26"
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
@ -81,31 +79,31 @@ categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(
|
||||
If you are sure you don't want the reverse relation you can use `skip_reverse=True`
|
||||
flag of the `ManyToMany`.
|
||||
|
||||
If you set `skip_reverse` flag internally the field is still registered on the other
|
||||
side of the relationship so you can:
|
||||
If you set `skip_reverse` flag internally the field is still registered on the other
|
||||
side of the relationship so you can:
|
||||
|
||||
* `filter` by related models fields from reverse model
|
||||
* `order_by` by related models fields from reverse model
|
||||
|
||||
But you cannot:
|
||||
But you cannot:
|
||||
|
||||
* access the related field from reverse model with `related_name`
|
||||
* even if you `select_related` from reverse side of the model the returned models won't be populated in reversed instance (the join is not prevented so you still can `filter` and `order_by` over the relation)
|
||||
* the relation won't be populated in `dict()` and `json()`
|
||||
* the relation won't be populated in `model_dump()` and `json()`
|
||||
* you cannot pass the nested related objects when populating from dictionary or json (also through `fastapi`). It will be either ignored or error will be raised depending on `extra` setting in pydantic `Config`.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
@ -126,8 +124,8 @@ categories = (
|
||||
assert categories[0].first_name == "Test"
|
||||
|
||||
# note that posts are not populated for author even if explicitly
|
||||
# included in select_related - note no posts in dict()
|
||||
assert news.dict(exclude={"id"}) == {"name": "News"}
|
||||
# included in select_related - note no posts in model_dump()
|
||||
assert news.model_dump(exclude={"id"}) == {"name": "News"}
|
||||
|
||||
# still can filter through fields of related model
|
||||
categories = await Category.objects.filter(posts__title="Hello, M2M").all()
|
||||
@ -141,7 +139,7 @@ assert len(categories) == 1
|
||||
Optionally if you want to add additional fields you can explicitly create and pass
|
||||
the through model class.
|
||||
|
||||
```Python hl_lines="14-20 29"
|
||||
```Python hl_lines="19-24 32"
|
||||
--8<-- "../docs_src/relations/docs004.py"
|
||||
```
|
||||
|
||||
@ -170,9 +168,7 @@ So in example like this:
|
||||
```python
|
||||
... # course declaration omitted
|
||||
class Student(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -180,10 +176,7 @@ class Student(ormar.Model):
|
||||
|
||||
# will produce default Through model like follows (example simplified)
|
||||
class StudentCourse(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
tablename = "students_courses"
|
||||
ormar_config = base_ormar_config.copy(tablename="students_courses")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
student = ormar.ForeignKey(Student) # default name
|
||||
@ -199,10 +192,14 @@ Example:
|
||||
|
||||
```python
|
||||
... # course declaration omitted
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database("sqlite:///db.sqlite"),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -212,10 +209,7 @@ class Student(ormar.Model):
|
||||
|
||||
# will produce Through model like follows (example simplified)
|
||||
class StudentCourse(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
tablename = "students_courses"
|
||||
ormar_config = base_ormar_config.copy(tablename="student_courses")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
student_id = ormar.ForeignKey(Student) # set by through_relation_name
|
||||
@ -238,7 +232,7 @@ so it's useful only when additional fields are provided on `Through` model.
|
||||
|
||||
In a sample model setup as following:
|
||||
|
||||
```Python hl_lines="14-20 29"
|
||||
```Python hl_lines="19-24 32"
|
||||
--8<-- "../docs_src/relations/docs004.py"
|
||||
```
|
||||
|
||||
|
||||
@ -14,21 +14,8 @@ First, you need to import the required ref from typing.
|
||||
from typing import ForwardRef
|
||||
```
|
||||
|
||||
But note that before python 3.7 it used to be internal, so for python <= 3.6 you need
|
||||
|
||||
```python
|
||||
from typing import _ForwardRef as ForwardRef
|
||||
```
|
||||
|
||||
or since `pydantic` is required by `ormar` it can handle this switch for you.
|
||||
In that case you can simply import ForwardRef from pydantic regardless of your python version.
|
||||
|
||||
```python
|
||||
from pydantic.typing import ForwardRef
|
||||
```
|
||||
|
||||
Now we need a sample model and a reference to the same model,
|
||||
which will be used to creat a self referencing relation.
|
||||
which will be used to create a self referencing relation.
|
||||
|
||||
```python
|
||||
# create the forwardref to model Person
|
||||
@ -36,9 +23,7 @@ PersonRef = ForwardRef("Person")
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -72,9 +57,7 @@ PersonRef = ForwardRef("Person")
|
||||
|
||||
|
||||
class Person(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -93,14 +76,10 @@ and through parameters.
|
||||
ChildRef = ForwardRef("Child")
|
||||
|
||||
class ChildFriend(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
class Child(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -132,9 +111,7 @@ TeacherRef = ForwardRef("Teacher")
|
||||
|
||||
|
||||
class Student(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -144,16 +121,11 @@ class Student(ormar.Model):
|
||||
|
||||
|
||||
class StudentTeacher(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
tablename = 'students_x_teachers'
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy(tablename='students_x_teachers')
|
||||
|
||||
|
||||
class Teacher(ormar.Model):
|
||||
class Meta(ModelMeta):
|
||||
metadata = metadata
|
||||
database = db
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -58,7 +58,7 @@ assert post.categories[0] == news
|
||||
|
||||
`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]`
|
||||
|
||||
Tries to get a row meeting the criteria and if NoMatch exception is raised it creates a new one with given kwargs and _defaults.
|
||||
Tries to get a row meeting the criteria and if `NoMatch` exception is raised it creates a new one with given kwargs and _defaults.
|
||||
|
||||
!!!tip
|
||||
Read more in queries documentation [get_or_create][get_or_create]
|
||||
@ -127,7 +127,7 @@ provided Through model.
|
||||
|
||||
Given sample like this:
|
||||
|
||||
```Python hl_lines="14-20 29"
|
||||
```Python hl_lines="19-24 32"
|
||||
--8<-- "../docs_src/relations/docs004.py"
|
||||
```
|
||||
|
||||
@ -174,7 +174,7 @@ Updates the related model with provided keyword arguments, return number of upda
|
||||
Note that for `ManyToMany` relations update can also accept an argument with through field
|
||||
name and a dictionary of fields.
|
||||
|
||||
```Python hl_lines="14-20 29"
|
||||
```Python hl_lines="19-24 32"
|
||||
--8<-- "../docs_src/relations/docs004.py"
|
||||
```
|
||||
|
||||
|
||||
676
docs/releases.md
676
docs/releases.md
File diff suppressed because it is too large
Load Diff
@ -14,15 +14,15 @@ import sqlalchemy
|
||||
|
||||
import ormar
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database("sqlite:///db.sqlite"),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -34,7 +34,7 @@ You can for example define a trigger that will set `album.is_best_seller` status
|
||||
|
||||
Import `pre_update` decorator, for list of currently available decorators/ signals check below.
|
||||
|
||||
```Python hl_lines="1"
|
||||
```Python hl_lines="7"
|
||||
--8<-- "../docs_src/signals/docs002.py"
|
||||
```
|
||||
|
||||
@ -54,7 +54,7 @@ for which you want to run the signal receiver.
|
||||
|
||||
Currently there is no way to set signal for all models at once without explicitly passing them all into registration of receiver.
|
||||
|
||||
```Python hl_lines="4-7"
|
||||
```Python hl_lines="28-31"
|
||||
--8<-- "../docs_src/signals/docs002.py"
|
||||
```
|
||||
|
||||
@ -65,7 +65,7 @@ Currently there is no way to set signal for all models at once without explicitl
|
||||
Note that our newly created function has instance and class of the instance so you can easily run database
|
||||
queries inside your receivers if you want to.
|
||||
|
||||
```Python hl_lines="15-22"
|
||||
```Python hl_lines="41-48"
|
||||
--8<-- "../docs_src/signals/docs002.py"
|
||||
```
|
||||
|
||||
@ -75,15 +75,15 @@ You can define same receiver for multiple models at once by passing a list of mo
|
||||
# define a dummy debug function
|
||||
@pre_update([Album, Track])
|
||||
async def before_update(sender, instance, **kwargs):
|
||||
print(f"{sender.get_name()}: {instance.json()}: {kwargs}")
|
||||
print(f"{sender.get_name()}: {instance.model_dump_json()}: {kwargs}")
|
||||
```
|
||||
|
||||
Of course you can also create multiple functions for the same signal and model. Each of them will run at each signal.
|
||||
Of course, you can also create multiple functions for the same signal and model. Each of them will run at each signal.
|
||||
|
||||
```python
|
||||
@pre_update(Album)
|
||||
async def before_update(sender, instance, **kwargs):
|
||||
print(f"{sender.get_name()}: {instance.json()}: {kwargs}")
|
||||
print(f"{sender.get_name()}: {instance.model_dump_json()}: {kwargs}")
|
||||
|
||||
@pre_update(Album)
|
||||
async def before_update2(sender, instance, **kwargs):
|
||||
@ -100,13 +100,13 @@ class AlbumAuditor:
|
||||
|
||||
async def before_save(self, sender, instance, **kwargs):
|
||||
await AuditLog(
|
||||
event_type=f"{self.event_type}_SAVE", event_log=instance.json()
|
||||
event_type=f"{self.event_type}_SAVE", event_log=instance.model_dump_json()
|
||||
).save()
|
||||
|
||||
auditor = AlbumAuditor()
|
||||
pre_save(Album)(auditor.before_save)
|
||||
# call above has same result like the one below
|
||||
Album.Meta.signals.pre_save.connect(auditor.before_save)
|
||||
Album.ormar_config.signals.pre_save.connect(auditor.before_save)
|
||||
# signals are also exposed on instance
|
||||
album = Album(name='Miami')
|
||||
album.signals.pre_save.connect(auditor.before_save)
|
||||
@ -127,7 +127,7 @@ async def before_update(sender, instance, **kwargs):
|
||||
instance.is_best_seller = True
|
||||
|
||||
# disconnect given function from signal for given Model
|
||||
Album.Meta.signals.pre_save.disconnect(before_save)
|
||||
Album.ormar_config.signals.pre_save.disconnect(before_save)
|
||||
# signals are also exposed on instance
|
||||
album = Album(name='Miami')
|
||||
album.signals.pre_save.disconnect(before_save)
|
||||
@ -142,7 +142,7 @@ album.signals.pre_save.disconnect(before_save)
|
||||
* bulk operations (`QuerySet.bulk_create` and `QuerySet.bulk_update`) as they are designed for speed.
|
||||
|
||||
* queryset table level operations (`QuerySet.update` and `QuerySet.delete`) as they run on the underlying tables
|
||||
(more lak raw sql update/delete operations) and do not have specific instance.
|
||||
(more like raw sql update/delete operations) and do not have specific instance.
|
||||
|
||||
### pre_save
|
||||
|
||||
@ -251,23 +251,23 @@ import sqlalchemy
|
||||
|
||||
import ormar
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database("sqlite:///db.sqlite"),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
play_count: int = ormar.Integer(default=0)
|
||||
|
||||
Album.Meta.signals.your_custom_signal = ormar.Signal()
|
||||
Album.Meta.signals.your_custom_signal.connect(your_receiver_name)
|
||||
Album.ormar_config.signals.your_custom_signal = ormar.Signal()
|
||||
Album.ormar_config.signals.your_custom_signal.connect(your_receiver_name)
|
||||
```
|
||||
|
||||
Actually under the hood signal is a `SignalEmitter` instance that keeps a dictionary of know signals, and allows you
|
||||
@ -276,13 +276,13 @@ to access them as attributes. When you try to access a signal that does not exis
|
||||
So example above can be simplified to. The `Signal` will be created for you.
|
||||
|
||||
```
|
||||
Album.Meta.signals.your_custom_signal.connect(your_receiver_name)
|
||||
Album.ormar_config.signals.your_custom_signal.connect(your_receiver_name)
|
||||
```
|
||||
|
||||
Now to trigger this signal you need to call send method of the Signal.
|
||||
|
||||
```python
|
||||
await Album.Meta.signals.your_custom_signal.send(sender=Album)
|
||||
await Album.ormar_config.signals.your_custom_signal.send(sender=Album)
|
||||
```
|
||||
|
||||
Note that sender is the only required parameter and it should be ormar Model class.
|
||||
@ -290,6 +290,6 @@ Note that sender is the only required parameter and it should be ormar Model cla
|
||||
Additional parameters have to be passed as keyword arguments.
|
||||
|
||||
```python
|
||||
await Album.Meta.signals.your_custom_signal.send(sender=Album, my_param=True)
|
||||
await Album.ormar_config.signals.your_custom_signal.send(sender=Album, my_param=True)
|
||||
```
|
||||
|
||||
|
||||
@ -15,10 +15,10 @@ async with database.transaction():
|
||||
```
|
||||
|
||||
!!!note
|
||||
Note that it has to be the same `database` that the one used in Model's `Meta` class.
|
||||
Note that it has to be the same `database` that the one used in Model's `ormar_config` object.
|
||||
|
||||
To avoid passing `database` instance around in your code you can extract the instance from each `Model`.
|
||||
Database provided during declaration of `ormar.Model` is available through `Meta.database` and can
|
||||
Database provided during declaration of `ormar.Model` is available through `ormar_config.database` and can
|
||||
be reached from both class and instance.
|
||||
|
||||
```python
|
||||
@ -26,24 +26,25 @@ import databases
|
||||
import sqlalchemy
|
||||
import ormar
|
||||
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database("sqlite:///")
|
||||
|
||||
base_ormar_config = OrmarConfig(
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
database = databases.Database("sqlite:///"),
|
||||
)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta:
|
||||
database=database
|
||||
metadata=metadata
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=255)
|
||||
|
||||
# database is accessible from class
|
||||
database = Author.Meta.database
|
||||
database = Author.ormar_config.database
|
||||
|
||||
# as well as from instance
|
||||
author = Author(name="Stephen King")
|
||||
database = author.Meta.database
|
||||
|
||||
database = author.ormar_config.database
|
||||
```
|
||||
|
||||
You can also use `.transaction()` as a function decorator on any async function:
|
||||
|
||||
@ -1,33 +1,32 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
metadata=metadata,
|
||||
database=database,
|
||||
)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
order_by = ["-name"]
|
||||
ormar_config = base_ormar_config.copy(tablename="authors", order_by=["-name"])
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
order_by = ["year", "-ranking"]
|
||||
|
||||
ormar_config = base_ormar_config.copy(
|
||||
tablename="books", order_by=["year", "-ranking"]
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
|
||||
@ -1,46 +1,23 @@
|
||||
from typing import List, Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
from fastapi import FastAPI
|
||||
|
||||
import ormar
|
||||
from fastapi import FastAPI
|
||||
from tests.lifespan import lifespan
|
||||
from tests.settings import create_config
|
||||
|
||||
app = FastAPI()
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database("sqlite:///test.db")
|
||||
app.state.database = database
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup() -> None:
|
||||
database_ = app.state.database
|
||||
if not database_.is_connected:
|
||||
await database_.connect()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown() -> None:
|
||||
database_ = app.state.database
|
||||
if database_.is_connected:
|
||||
await database_.disconnect()
|
||||
base_ormar_config = create_config()
|
||||
app = FastAPI(lifespan=lifespan(base_ormar_config))
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = base_ormar_config.copy(tablename="items")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -68,7 +45,7 @@ async def create_category(category: Category):
|
||||
@app.put("/items/{item_id}")
|
||||
async def get_item(item_id: int, item: Item):
|
||||
item_db = await Item.objects.get(pk=item_id)
|
||||
return await item_db.update(**item.dict())
|
||||
return await item_db.update(**item.model_dump())
|
||||
|
||||
|
||||
@app.delete("/items/{item_id}")
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id = ormar.Integer(primary_key=True)
|
||||
name = ormar.String(max_length=100)
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy(tablename="departments")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -29,12 +29,17 @@ class Course(ormar.Model):
|
||||
department: Optional[Department] = ormar.ForeignKey(Department)
|
||||
|
||||
|
||||
department = await Department(name="Science").save()
|
||||
course = Course(name="Math", completed=False, department=department)
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def verify():
|
||||
department = await Department(name="Science").save()
|
||||
course = Course(name="Math", completed=False, department=department)
|
||||
print(department.courses[0])
|
||||
# Will produce:
|
||||
# Course(id=None,
|
||||
# name='Math',
|
||||
# completed=False,
|
||||
# department=Department(id=None, name='Science'))
|
||||
await course.save()
|
||||
|
||||
print(department.courses[0])
|
||||
# Will produce:
|
||||
# Course(id=None,
|
||||
# name='Math',
|
||||
# completed=False,
|
||||
# department=Department(id=None, name='Science'))
|
||||
|
||||
asyncio.run(verify())
|
||||
|
||||
@ -1,32 +1,32 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
department: Optional[Department] = ormar.ForeignKey(Department, related_name="my_courses")
|
||||
department: Optional[Department] = ormar.ForeignKey(
|
||||
Department, related_name="my_courses"
|
||||
)
|
||||
|
||||
|
||||
department = Department(name="Science")
|
||||
|
||||
@ -1,27 +1,28 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
from datetime import datetime
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from sqlalchemy import func, text
|
||||
|
||||
import ormar
|
||||
|
||||
database = databases.Database("sqlite:///test.db")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Product(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "product"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database, metadata=metadata, tablename="product"
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
# if you omit this parameter it will be created automatically
|
||||
# as class.__name__.lower()+'s' -> "courses" in this example
|
||||
tablename = "my_courses"
|
||||
database = database
|
||||
metadata = metadata
|
||||
tablename="my_courses",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
|
||||
|
||||
print(Course.__fields__)
|
||||
print(Course.model_fields)
|
||||
"""
|
||||
Will produce:
|
||||
{'id': ModelField(name='id',
|
||||
{'id': Field(name='id',
|
||||
type=Optional[int],
|
||||
required=False,
|
||||
default=None),
|
||||
'name': ModelField(name='name',
|
||||
'name': Field(name='name',
|
||||
type=Optional[str],
|
||||
required=False,
|
||||
default=None),
|
||||
'completed': ModelField(name='completed',
|
||||
'completed': Field(name='completed',
|
||||
type=bool,
|
||||
required=False,
|
||||
default=False)}
|
||||
|
||||
@ -1,24 +1,28 @@
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta(ormar.ModelMeta): # note you don't have to subclass - but it's recommended for ide completion and mypy
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
tablename="courses",
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
|
||||
|
||||
print(Course.Meta.table.columns)
|
||||
print(Course.ormar_config.table.columns)
|
||||
"""
|
||||
Will produce:
|
||||
['courses.id', 'courses.name', 'courses.completed']
|
||||
ImmutableColumnCollection(courses.id, courses.name, courses.completed)
|
||||
"""
|
||||
|
||||
@ -1,67 +1,144 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
import pprint
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
|
||||
|
||||
print({x: v.__dict__ for x, v in Course.Meta.model_fields.items()})
|
||||
pprint.pp({x: v.__dict__ for x, v in Course.ormar_config.model_fields.items()})
|
||||
"""
|
||||
Will produce:
|
||||
{'completed': mappingproxy({'autoincrement': False,
|
||||
'choices': set(),
|
||||
'column_type': Boolean(),
|
||||
'default': False,
|
||||
'index': False,
|
||||
'name': 'completed',
|
||||
'nullable': True,
|
||||
'primary_key': False,
|
||||
'pydantic_only': False,
|
||||
'server_default': None,
|
||||
'unique': False}),
|
||||
'id': mappingproxy({'autoincrement': True,
|
||||
'choices': set(),
|
||||
'column_type': Integer(),
|
||||
'default': None,
|
||||
'ge': None,
|
||||
'index': False,
|
||||
'le': None,
|
||||
'maximum': None,
|
||||
'minimum': None,
|
||||
'multiple_of': None,
|
||||
'name': 'id',
|
||||
'nullable': False,
|
||||
'primary_key': True,
|
||||
'pydantic_only': False,
|
||||
'server_default': None,
|
||||
'unique': False}),
|
||||
'name': mappingproxy({'allow_blank': False,
|
||||
'autoincrement': False,
|
||||
'choices': set(),
|
||||
'column_type': String(max_length=100),
|
||||
'curtail_length': None,
|
||||
'default': None,
|
||||
'index': False,
|
||||
'max_length': 100,
|
||||
'min_length': None,
|
||||
'name': 'name',
|
||||
'nullable': False,
|
||||
'primary_key': False,
|
||||
'pydantic_only': False,
|
||||
'regex': None,
|
||||
'server_default': None,
|
||||
'strip_whitespace': False,
|
||||
'unique': False})}
|
||||
{'id': {'__type__': <class 'int'>,
|
||||
'__pydantic_type__': <class 'int'>,
|
||||
'__sample__': 0,
|
||||
'related_name': None,
|
||||
'column_type': Integer(),
|
||||
'constraints': [],
|
||||
'name': 'id',
|
||||
'db_alias': None,
|
||||
'primary_key': True,
|
||||
'autoincrement': True,
|
||||
'nullable': True,
|
||||
'sql_nullable': False,
|
||||
'index': False,
|
||||
'unique': False,
|
||||
'virtual': None,
|
||||
'is_multi': None,
|
||||
'is_relation': None,
|
||||
'is_through': False,
|
||||
'through_relation_name': None,
|
||||
'through_reverse_relation_name': None,
|
||||
'skip_reverse': False,
|
||||
'skip_field': False,
|
||||
'owner': <class '__main__.Course'>,
|
||||
'to': None,
|
||||
'to_pk_only': None,
|
||||
'through': None,
|
||||
'self_reference': False,
|
||||
'self_reference_primary': None,
|
||||
'orders_by': None,
|
||||
'related_orders_by': None,
|
||||
'encrypt_secret': None,
|
||||
'encrypt_backend': <EncryptBackends.NONE: 0>,
|
||||
'encrypt_custom_backend': None,
|
||||
'ormar_default': None,
|
||||
'server_default': None,
|
||||
'comment': None,
|
||||
'represent_as_base64_str': False,
|
||||
'minimum': None,
|
||||
'maximum': None,
|
||||
'multiple_of': None,
|
||||
'ge': None,
|
||||
'le': None},
|
||||
'name': {'__type__': <class 'str'>,
|
||||
'__pydantic_type__': <class 'str'>,
|
||||
'__sample__': 'string',
|
||||
'related_name': None,
|
||||
'column_type': String(length=100),
|
||||
'constraints': [],
|
||||
'name': 'name',
|
||||
'db_alias': None,
|
||||
'primary_key': False,
|
||||
'autoincrement': False,
|
||||
'nullable': False,
|
||||
'sql_nullable': False,
|
||||
'index': False,
|
||||
'unique': False,
|
||||
'virtual': None,
|
||||
'is_multi': None,
|
||||
'is_relation': None,
|
||||
'is_through': False,
|
||||
'through_relation_name': None,
|
||||
'through_reverse_relation_name': None,
|
||||
'skip_reverse': False,
|
||||
'skip_field': False,
|
||||
'owner': <class '__main__.Course'>,
|
||||
'to': None,
|
||||
'to_pk_only': None,
|
||||
'through': None,
|
||||
'self_reference': False,
|
||||
'self_reference_primary': None,
|
||||
'orders_by': None,
|
||||
'related_orders_by': None,
|
||||
'encrypt_secret': None,
|
||||
'encrypt_backend': <EncryptBackends.NONE: 0>,
|
||||
'encrypt_custom_backend': None,
|
||||
'ormar_default': None,
|
||||
'server_default': None,
|
||||
'comment': None,
|
||||
'represent_as_base64_str': False,
|
||||
'max_length': 100,
|
||||
'min_length': None,
|
||||
'regex': None},
|
||||
'completed': {'__type__': <class 'bool'>,
|
||||
'__pydantic_type__': <class 'bool'>,
|
||||
'__sample__': True,
|
||||
'related_name': None,
|
||||
'column_type': Boolean(),
|
||||
'constraints': [],
|
||||
'name': 'completed',
|
||||
'db_alias': None,
|
||||
'primary_key': False,
|
||||
'autoincrement': False,
|
||||
'nullable': True,
|
||||
'sql_nullable': True,
|
||||
'index': False,
|
||||
'unique': False,
|
||||
'virtual': None,
|
||||
'is_multi': None,
|
||||
'is_relation': None,
|
||||
'is_through': False,
|
||||
'through_relation_name': None,
|
||||
'through_reverse_relation_name': None,
|
||||
'skip_reverse': False,
|
||||
'skip_field': False,
|
||||
'owner': <class '__main__.Course'>,
|
||||
'to': None,
|
||||
'to_pk_only': None,
|
||||
'through': None,
|
||||
'self_reference': False,
|
||||
'self_reference_primary': None,
|
||||
'orders_by': None,
|
||||
'related_orders_by': None,
|
||||
'encrypt_secret': None,
|
||||
'encrypt_backend': <EncryptBackends.NONE: 0>,
|
||||
'encrypt_custom_backend': None,
|
||||
'ormar_default': False,
|
||||
'server_default': None,
|
||||
'comment': None,
|
||||
'represent_as_base64_str': False}}
|
||||
"""
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
# define your constraints in Meta class of the model
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
# define your constraints in OrmarConfig of the model
|
||||
# it's a list that can contain multiple constraints
|
||||
# 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: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,23 +1,31 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy(tablename="courses")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
|
||||
|
||||
course = Course(name="Painting for dummies", completed=False)
|
||||
await course.save()
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
course = Course(name="Painting for dummies", completed=False)
|
||||
await course.save()
|
||||
|
||||
await Course.objects.create(name="Painting for dummies", completed=False)
|
||||
await Course.objects.create(name="Painting for dummies", completed=False)
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
DATABASE_URl = "sqlite:///test.db"
|
||||
|
||||
database = databases.Database("sqlite:///test.db", force_rollback=True)
|
||||
database = databases.Database(DATABASE_URl, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Child(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "children"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="children",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(name="child_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
|
||||
@ -1,20 +1,33 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from .docs010 import Artist # previous example
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///test.db", force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Artist(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="artists",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(name="artist_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
last_name: str = ormar.String(name="lname", max_length=100)
|
||||
born_year: int = ormar.Integer(name="year")
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "music_albums"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="music_albums",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(name="album_id", primary_key=True)
|
||||
name: str = ormar.String(name="album_name", max_length=100)
|
||||
|
||||
@ -1,25 +1,40 @@
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from .docs008 import Child
|
||||
DATABASE_URl = "sqlite:///test.db"
|
||||
|
||||
database = databases.Database("sqlite:///test.db", force_rollback=True)
|
||||
database = databases.Database(DATABASE_URl, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Child(ormar.Model):
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="children",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(name="child_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
last_name: str = ormar.String(name="lname", max_length=100)
|
||||
born_year: int = ormar.Integer(name="year_born", nullable=True)
|
||||
|
||||
|
||||
class ArtistChildren(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "children_x_artists"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="children_x_artists",
|
||||
)
|
||||
|
||||
|
||||
class Artist(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "artists"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
tablename="artists",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(name="artist_id", primary_key=True)
|
||||
first_name: str = ormar.String(name="fname", max_length=100)
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
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()
|
||||
@ -1,16 +1,16 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id = ormar.Integer(primary_key=True)
|
||||
name = ormar.String(max_length=100)
|
||||
|
||||
@ -1,27 +1,22 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
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
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
# note that tablename is optional
|
||||
# if not provided ormar will user class.__name__.lower()+'s'
|
||||
# -> artists in this example
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
first_name: str = ormar.String(max_length=100)
|
||||
@ -30,8 +25,7 @@ class Artist(ormar.Model):
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta(MainMeta):
|
||||
pass
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import pydantic
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
non_db_field: str = ormar.String(max_length=100, pydantic_only=True)
|
||||
non_db_field: str = pydantic.Field(max_length=100)
|
||||
|
||||
@ -1,22 +1,21 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from ormar import property_field
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
completed: bool = ormar.Boolean(default=False)
|
||||
|
||||
@property_field
|
||||
@property
|
||||
def prefixed_name(self):
|
||||
return "custom_prefix__" + self.name
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import pydantic
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
class Config:
|
||||
allow_mutation = False
|
||||
model_config = pydantic.ConfigDict(frozen=True)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
# define your constraints in Meta class of the model
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
# define your constraints in OrmarConfig of the model
|
||||
# it's a list that can contain multiple constraints
|
||||
# hera a combination of name and column will have a compound index in the db
|
||||
constraints = [ormar.IndexColumns("name", "completed")]
|
||||
constraints=[ormar.IndexColumns("name", "completed")],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
import datetime
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
# define your constraints in Meta class of the model
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
# define your constraints in OrmarConfig of the model
|
||||
# it's a list that can contain multiple constraints
|
||||
# hera a combination of name and column will have a level check in the db
|
||||
constraints = [
|
||||
constraints=[
|
||||
ormar.CheckColumns("start_time < end_time", name="date_check"),
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -4,25 +4,23 @@ import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "album"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="album")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Track(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "track"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="track")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
album: Optional[Album] = ormar.ForeignKey(Album)
|
||||
|
||||
@ -1,28 +1,47 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "books"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(
|
||||
tablename="books",
|
||||
)
|
||||
|
||||
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'])
|
||||
genre: str = ormar.String(
|
||||
max_length=100,
|
||||
default="Fiction",
|
||||
)
|
||||
|
||||
|
||||
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')
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
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
|
||||
await Book.objects.update(each=True, genre="Fiction")
|
||||
all_books = await Book.objects.filter(genre="Fiction").all()
|
||||
assert len(all_books) == 3
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,32 +1,51 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "books"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
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'])
|
||||
genre: str = ormar.String(
|
||||
max_length=100,
|
||||
default="Fiction",
|
||||
)
|
||||
|
||||
|
||||
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')
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
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 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() == 4
|
||||
|
||||
# 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
|
||||
# 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() == 4
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,30 +1,39 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class ToDo(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "todos"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="todos")
|
||||
|
||||
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),
|
||||
]
|
||||
)
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
# 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
|
||||
todoes = await ToDo.objects.all()
|
||||
assert len(todoes) == 3
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,30 +1,47 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "books"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="books")
|
||||
|
||||
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'])
|
||||
genre: str = ormar.String(
|
||||
max_length=100,
|
||||
default="Fiction",
|
||||
)
|
||||
|
||||
|
||||
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')
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
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
|
||||
# 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
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Company(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "companies"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="companies")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -20,10 +22,7 @@ class Company(ormar.Model):
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "cars"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="cars")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
manufacturer = ormar.ForeignKey(Company)
|
||||
@ -34,12 +33,34 @@ class Car(ormar.Model):
|
||||
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')
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
# 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",
|
||||
)
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,42 +1,46 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Owner(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "owners"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="owners")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Toy(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "toys"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="toys")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
owner: Owner = ormar.ForeignKey(Owner)
|
||||
|
||||
|
||||
# build some sample data
|
||||
aphrodite = await Owner.objects.create(name="Aphrodite")
|
||||
hermes = await Owner.objects.create(name="Hermes")
|
||||
zeus = await Owner.objects.create(name="Zeus")
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
# build some sample data
|
||||
aphrodite = await Owner.objects.create(name="Aphrodite")
|
||||
hermes = await Owner.objects.create(name="Hermes")
|
||||
zeus = await Owner.objects.create(name="Zeus")
|
||||
|
||||
await Toy.objects.create(name="Toy 4", owner=zeus)
|
||||
await Toy.objects.create(name="Toy 5", owner=hermes)
|
||||
await Toy.objects.create(name="Toy 2", owner=aphrodite)
|
||||
await Toy.objects.create(name="Toy 1", owner=zeus)
|
||||
await Toy.objects.create(name="Toy 3", owner=aphrodite)
|
||||
await Toy.objects.create(name="Toy 6", owner=hermes)
|
||||
await Toy.objects.create(name="Toy 4", owner=zeus)
|
||||
await Toy.objects.create(name="Toy 5", owner=hermes)
|
||||
await Toy.objects.create(name="Toy 2", owner=aphrodite)
|
||||
await Toy.objects.create(name="Toy 1", owner=zeus)
|
||||
await Toy.objects.create(name="Toy 3", owner=aphrodite)
|
||||
await Toy.objects.create(name="Toy 6", owner=hermes)
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
from tests.settings import DATABASE_URL
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
from pydantic import ValidationError
|
||||
|
||||
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Company(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "companies"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="companies")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -20,10 +23,7 @@ class Company(ormar.Model):
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "cars"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="cars")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
manufacturer = ormar.ForeignKey(Company)
|
||||
@ -34,35 +34,81 @@ class Car(ormar.Model):
|
||||
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')
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
# 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').exclude_fields(
|
||||
['year', 'gearbox_type', 'gears', 'aircon_type', 'company__founded']).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
|
||||
# select manufacturer but only name,
|
||||
# to include related models use notation {model_name}__{column}
|
||||
all_cars = (
|
||||
await Car.objects.select_related("manufacturer")
|
||||
.exclude_fields(
|
||||
["year", "gearbox_type", "gears", "aircon_type", "manufacturer__founded"]
|
||||
)
|
||||
.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').exclude_fields('year').exclude_fields(
|
||||
['gear', 'gearbox_type']).all()
|
||||
# all fields from company model are selected
|
||||
assert all_cars[0].manufacturer.name == 'Toyota'
|
||||
assert all_cars[0].manufacturer.founded == 1937
|
||||
# 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")
|
||||
.exclude_fields("year")
|
||||
.exclude_fields(["gear", "gearbox_type"])
|
||||
.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 - note usage of dict/set this time
|
||||
await Car.objects.select_related('manufacturer').exclude_fields([{'company': {'name'}}]).all()
|
||||
# will raise pydantic ValidationError as company.name is required
|
||||
# cannot exclude mandatory model columns -
|
||||
# manufacturer__name in this example - note usage of dict/set this time
|
||||
try:
|
||||
await Car.objects.select_related("manufacturer").exclude_fields(
|
||||
{"manufacturer": {"name"}}
|
||||
).all()
|
||||
except ValidationError:
|
||||
# will raise pydantic ValidationError as company.name is required
|
||||
pass
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,33 +1,71 @@
|
||||
# 1. like in example above
|
||||
await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all()
|
||||
import asyncio
|
||||
|
||||
# 2. to mark a field as required use ellipsis
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {
|
||||
'name': ...}
|
||||
}).all()
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
|
||||
# 3. to include whole nested model use ellipsis
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': ...
|
||||
}).all()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
# 4. to specify fields at last nesting level you can also use set - equivalent to 2. above
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'name'}
|
||||
}).all()
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
# 5. of course set can have multiple fields
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'name', 'founded'}
|
||||
}).all()
|
||||
|
||||
# 6. you can include all nested fields but it will be equivalent of 3. above which is shorter
|
||||
await Car.objects.select_related('manufacturer').fields({'id': ...,
|
||||
'name': ...,
|
||||
'manufacturer': {'id', 'name', 'founded'}
|
||||
}).all()
|
||||
class Company(ormar.Model):
|
||||
ormar_config = ormar_base_config.copy(tablename="companies")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
founded: int = ormar.Integer(nullable=True)
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
ormar_config = ormar_base_config.copy(tablename="cars")
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
# 1. like in example above
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
["id", "name", "manufacturer__name"]
|
||||
).all()
|
||||
|
||||
# 2. to mark a field as required use ellipsis
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
{"id": ..., "name": ..., "manufacturer": {"name": ...}}
|
||||
).all()
|
||||
|
||||
# 3. to include whole nested model use ellipsis
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
{"id": ..., "name": ..., "manufacturer": ...}
|
||||
).all()
|
||||
|
||||
# 4. to specify fields at last nesting level you can also use set
|
||||
# - equivalent to 2. above
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
{"id": ..., "name": ..., "manufacturer": {"name"}}
|
||||
).all()
|
||||
|
||||
# 5. of course set can have multiple fields
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
{"id": ..., "name": ..., "manufacturer": {"name", "founded"}}
|
||||
).all()
|
||||
|
||||
# 6. you can include all nested fields,
|
||||
# but it will be equivalent of 3. above which is shorter
|
||||
await Car.objects.select_related("manufacturer").fields(
|
||||
{"id": ..., "name": ..., "manufacturer": {"id", "name", "founded"}}
|
||||
).all()
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
@ -1,27 +1,28 @@
|
||||
from typing import Optional, Dict, Union
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
import databases
|
||||
import sqlalchemy
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar.OrmarConfig(
|
||||
database=database,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -38,7 +39,7 @@ course = Course(name="Math", completed=False, department=department)
|
||||
course2 = Course(name="Math II", completed=False, department=department.pk)
|
||||
|
||||
# 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.model_dump())
|
||||
|
||||
# explicitly set up None
|
||||
course4 = Course(name="Math III", completed=False, department=None)
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
from typing import Optional, Union, List
|
||||
from typing import List, Optional
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
database = databases.Database("sqlite:///db.sqlite")
|
||||
metadata = sqlalchemy.MetaData()
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "authors"
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy(tablename="authors")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
first_name: str = ormar.String(max_length=80)
|
||||
@ -20,20 +20,14 @@ class Author(ormar.Model):
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=40)
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "posts"
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy(tablename="posts")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
|
||||
@ -1,16 +1,25 @@
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Department(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Course(ormar.Model):
|
||||
class Meta:
|
||||
database = database
|
||||
metadata = metadata
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
database = database
|
||||
metadata = metadata
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "categories"
|
||||
ormar_config = ormar_base_config.copy(tablename="categories")
|
||||
|
||||
id = ormar.Integer(primary_key=True)
|
||||
name = ormar.String(max_length=40)
|
||||
|
||||
|
||||
class PostCategory(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "posts_x_categories"
|
||||
ormar_config = ormar_base_config.copy(tablename="posts_x_categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
sort_order: int = ormar.Integer(nullable=True)
|
||||
@ -21,8 +25,7 @@ class PostCategory(ormar.Model):
|
||||
|
||||
|
||||
class Post(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
pass
|
||||
ormar_config = ormar_base_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
title: str = ormar.String(max_length=200)
|
||||
|
||||
0
docs_src/select_columns/__init__.py
Normal file
0
docs_src/select_columns/__init__.py
Normal file
65
docs_src/select_columns/docs001.py
Normal file
65
docs_src/select_columns/docs001.py
Normal file
@ -0,0 +1,65 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
from tests.settings import DATABASE_URL
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL, force_rollback=True),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Company(ormar.Model):
|
||||
ormar_config = base_ormar_config.copy(tablename="companies")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
founded: int = ormar.Integer(nullable=True)
|
||||
|
||||
|
||||
class Car(ormar.Model):
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@create_drop_database(base_config=base_ormar_config)
|
||||
async def sample_data():
|
||||
# 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",
|
||||
)
|
||||
|
||||
|
||||
asyncio.run(sample_data())
|
||||
@ -1,5 +1,29 @@
|
||||
import asyncio
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
from examples import create_drop_database
|
||||
from ormar import pre_update
|
||||
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
)
|
||||
|
||||
|
||||
class Album(ormar.Model):
|
||||
ormar_config = ormar_base_config.copy(
|
||||
tablename="albums",
|
||||
)
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
is_best_seller: bool = ormar.Boolean(default=False)
|
||||
play_count: int = ormar.Integer(default=0)
|
||||
|
||||
|
||||
@pre_update(Album)
|
||||
async def before_update(sender, instance, **kwargs):
|
||||
@ -7,16 +31,21 @@ async def before_update(sender, instance, **kwargs):
|
||||
instance.is_best_seller = True
|
||||
|
||||
|
||||
# here album.play_count ans is_best_seller get default values
|
||||
album = await Album.objects.create(name="Venice")
|
||||
assert not album.is_best_seller
|
||||
assert album.play_count == 0
|
||||
@create_drop_database(base_config=ormar_base_config)
|
||||
async def run_query():
|
||||
# here album.play_count ans is_best_seller get default values
|
||||
album = await Album.objects.create(name="Venice")
|
||||
assert not album.is_best_seller
|
||||
assert album.play_count == 0
|
||||
|
||||
album.play_count = 30
|
||||
# here a trigger is called but play_count is too low
|
||||
await album.update()
|
||||
assert not album.is_best_seller
|
||||
album.play_count = 30
|
||||
# here a trigger is called but play_count is too low
|
||||
await album.update()
|
||||
assert not album.is_best_seller
|
||||
|
||||
album.play_count = 60
|
||||
await album.update()
|
||||
assert album.is_best_seller
|
||||
album.play_count = 60
|
||||
await album.update()
|
||||
assert album.is_best_seller
|
||||
|
||||
|
||||
asyncio.run(run_query())
|
||||
|
||||
20
docs_src/test_all_docs.py
Normal file
20
docs_src/test_all_docs.py
Normal file
@ -0,0 +1,20 @@
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
filepaths = []
|
||||
path = Path(__file__).parent
|
||||
for p in path.rglob("*"):
|
||||
print(p.name)
|
||||
for p in path.rglob("*"):
|
||||
if p.name.endswith(".py") and not p.name == "__init__.py" and p != Path(__file__):
|
||||
filepath_ = str(p.resolve())
|
||||
filepaths.append(filepath_)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filepath", filepaths)
|
||||
def test_all_docs(filepath: str):
|
||||
result = subprocess.run([sys.executable, filepath])
|
||||
assert result.returncode == 0
|
||||
3
examples/__init__.py
Normal file
3
examples/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .utils import create_drop_database
|
||||
|
||||
__all__ = ["create_drop_database"]
|
||||
@ -1,47 +1,45 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Optional
|
||||
|
||||
import databases
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
|
||||
import ormar
|
||||
DATABASE_URL = "sqlite:///test.db"
|
||||
|
||||
app = FastAPI()
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database("sqlite:///test.db")
|
||||
app.state.database = database
|
||||
ormar_base_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
|
||||
)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup() -> None:
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
database_ = app.state.database
|
||||
if not database_.is_connected:
|
||||
await database_.connect()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown() -> None:
|
||||
yield
|
||||
database_ = app.state.database
|
||||
if database_.is_connected:
|
||||
await database_.disconnect()
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
database = databases.Database("sqlite:///test.db")
|
||||
app.state.database = database
|
||||
|
||||
|
||||
class Category(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "categories"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="categories")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Item(ormar.Model):
|
||||
class Meta:
|
||||
tablename = "items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
ormar_config = ormar_base_config.copy(tablename="items")
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
@ -69,7 +67,7 @@ async def create_category(category: Category):
|
||||
@app.put("/items/{item_id}")
|
||||
async def get_item(item_id: int, item: Item):
|
||||
item_db = await Item.objects.get(pk=item_id)
|
||||
return await item_db.update(**item.dict())
|
||||
return await item_db.update(**item.model_dump())
|
||||
|
||||
|
||||
@app.delete("/items/{item_id}")
|
||||
|
||||
@ -1,45 +1,40 @@
|
||||
from typing import Optional
|
||||
|
||||
import databases
|
||||
import pydantic
|
||||
|
||||
import ormar
|
||||
import pydantic
|
||||
import sqlalchemy
|
||||
|
||||
DATABASE_URL = "sqlite:///db.sqlite"
|
||||
database = databases.Database(DATABASE_URL)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
|
||||
# note that this step is optional -> all ormar cares is a internal
|
||||
# class with name Meta and proper parameters, but this way you do not
|
||||
# note that this step is optional -> all ormar cares is an individual
|
||||
# OrmarConfig for each of the models, but this way you do not
|
||||
# have to repeat the same parameters if you use only one database
|
||||
class BaseMeta(ormar.ModelMeta):
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
base_ormar_config = ormar.OrmarConfig(
|
||||
database=databases.Database(DATABASE_URL),
|
||||
metadata=sqlalchemy.MetaData(),
|
||||
engine=sqlalchemy.create_engine(DATABASE_URL),
|
||||
)
|
||||
|
||||
# Note that all type hints are optional
|
||||
# below is a perfectly valid model declaration
|
||||
# class Author(ormar.Model):
|
||||
# class Meta(BaseMeta):
|
||||
# tablename = "authors"
|
||||
# ormar_config = base_ormar_config.copy(tablename="authors")
|
||||
#
|
||||
# id = ormar.Integer(primary_key=True) # <= notice no field types
|
||||
# name = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Author(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "authors"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
name: str = ormar.String(max_length=100)
|
||||
|
||||
|
||||
class Book(ormar.Model):
|
||||
class Meta(BaseMeta):
|
||||
tablename = "books"
|
||||
ormar_config = base_ormar_config.copy()
|
||||
|
||||
id: int = ormar.Integer(primary_key=True)
|
||||
author: Optional[Author] = ormar.ForeignKey(Author)
|
||||
@ -50,10 +45,9 @@ class Book(ormar.Model):
|
||||
# create the database
|
||||
# note that in production you should use migrations
|
||||
# note that this is not required if you connect to existing database
|
||||
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||
# just to be sure we clear the db before
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
base_ormar_config.metadata.drop_all(base_ormar_config.engine)
|
||||
base_ormar_config.metadata.create_all(base_ormar_config.engine)
|
||||
|
||||
|
||||
# all functions below are divided into functionality categories
|
||||
@ -381,7 +375,7 @@ async def raw_data():
|
||||
async def with_connect(function):
|
||||
# note that for any other backend than sqlite you actually need to
|
||||
# connect to the database to perform db operations
|
||||
async with database:
|
||||
async with base_ormar_config.database:
|
||||
await function()
|
||||
|
||||
# note that if you use framework like `fastapi` you shouldn't connect
|
||||
@ -411,4 +405,4 @@ for func in [
|
||||
asyncio.run(with_connect(func))
|
||||
|
||||
# drop the database tables
|
||||
metadata.drop_all(engine)
|
||||
base_ormar_config.metadata.drop_all(base_ormar_config.engine)
|
||||
|
||||
21
examples/utils.py
Normal file
21
examples/utils.py
Normal file
@ -0,0 +1,21 @@
|
||||
import functools
|
||||
|
||||
import ormar
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def create_drop_database(base_config: ormar.OrmarConfig) -> None:
|
||||
# create all tables in the database before execution
|
||||
# and drop them after, note that in production you should use migrations
|
||||
def wrapper(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args):
|
||||
engine = sqlalchemy.create_engine(str(base_config.database.url))
|
||||
base_config.metadata.drop_all(engine)
|
||||
base_config.metadata.create_all(engine)
|
||||
await func(*args)
|
||||
base_config.metadata.drop_all(engine)
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrapper
|
||||
@ -16,10 +16,10 @@ nav:
|
||||
- Pydantic only fields: fields/pydantic-fields.md
|
||||
- Fields encryption: fields/encryption.md
|
||||
- Relations:
|
||||
- relations/index.md
|
||||
- relations/postponed-annotations.md
|
||||
- Relation types: relations/index.md
|
||||
- relations/foreign-key.md
|
||||
- relations/many-to-many.md
|
||||
- relations/postponed-annotations.md
|
||||
- relations/queryset-proxy.md
|
||||
- Queries:
|
||||
- queries/index.md
|
||||
@ -40,6 +40,7 @@ nav:
|
||||
- Using ormar in responses: fastapi/response.md
|
||||
- Using ormar in requests: fastapi/requests.md
|
||||
- Use with mypy: mypy.md
|
||||
- Migration to v 0.20: migration.md
|
||||
- PyCharm plugin: plugin.md
|
||||
- Contributing: contributing.md
|
||||
- Release Notes: releases.md
|
||||
|
||||
@ -19,11 +19,10 @@ snakes, and ormar(e) in italian which means cabinet.
|
||||
And what's a better name for python ORM than snakes cabinet :)
|
||||
|
||||
"""
|
||||
try:
|
||||
from importlib.metadata import version # type: ignore
|
||||
except ImportError: # pragma: no cover
|
||||
from importlib_metadata import version # type: ignore
|
||||
from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I100
|
||||
|
||||
from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I001
|
||||
from importlib.metadata import version
|
||||
|
||||
from ormar.decorators import ( # noqa: I100
|
||||
post_bulk_update,
|
||||
post_delete,
|
||||
@ -36,7 +35,6 @@ from ormar.decorators import ( # noqa: I100
|
||||
pre_relation_remove,
|
||||
pre_save,
|
||||
pre_update,
|
||||
property_field,
|
||||
)
|
||||
from ormar.exceptions import ( # noqa: I100
|
||||
ModelDefinitionError,
|
||||
@ -44,37 +42,38 @@ from ormar.exceptions import ( # noqa: I100
|
||||
NoMatch,
|
||||
)
|
||||
from ormar.fields import (
|
||||
DECODERS_MAP,
|
||||
ENCODERS_MAP,
|
||||
JSON,
|
||||
SQL_ENCODERS_MAP,
|
||||
UUID,
|
||||
BaseField,
|
||||
BigInteger,
|
||||
Boolean,
|
||||
DECODERS_MAP,
|
||||
CheckColumns,
|
||||
Date,
|
||||
DateTime,
|
||||
Decimal,
|
||||
ENCODERS_MAP,
|
||||
EncryptBackends,
|
||||
Enum,
|
||||
Float,
|
||||
ForeignKey,
|
||||
ForeignKeyField,
|
||||
IndexColumns,
|
||||
Integer,
|
||||
JSON,
|
||||
LargeBinary,
|
||||
ManyToMany,
|
||||
ManyToManyField,
|
||||
SQL_ENCODERS_MAP,
|
||||
ReferentialAction,
|
||||
SmallInteger,
|
||||
String,
|
||||
Text,
|
||||
Time,
|
||||
UUID,
|
||||
UniqueColumns,
|
||||
IndexColumns,
|
||||
CheckColumns,
|
||||
ReferentialAction,
|
||||
) # noqa: I100
|
||||
from ormar.models import ExcludableItems, Extra, Model
|
||||
from ormar.models.metaclass import ModelMeta
|
||||
)
|
||||
|
||||
# noqa: I100
|
||||
from ormar.models import ExcludableItems, Extra, Model, OrmarConfig
|
||||
from ormar.queryset import OrderAction, QuerySet, and_, or_
|
||||
from ormar.relations import RelationType
|
||||
from ormar.signals import Signal
|
||||
@ -104,7 +103,6 @@ __all__ = [
|
||||
"Float",
|
||||
"ManyToMany",
|
||||
"Model",
|
||||
"Action",
|
||||
"ModelDefinitionError",
|
||||
"MultipleMatches",
|
||||
"NoMatch",
|
||||
@ -119,8 +117,6 @@ __all__ = [
|
||||
"ReferentialAction",
|
||||
"QuerySetProtocol",
|
||||
"RelationProtocol",
|
||||
"ModelMeta",
|
||||
"property_field",
|
||||
"post_bulk_update",
|
||||
"post_delete",
|
||||
"post_save",
|
||||
@ -146,4 +142,5 @@ __all__ = [
|
||||
"DECODERS_MAP",
|
||||
"LargeBinary",
|
||||
"Extra",
|
||||
"OrmarConfig",
|
||||
]
|
||||
|
||||
@ -3,11 +3,10 @@ Module with all decorators that are exposed for users.
|
||||
|
||||
Currently only:
|
||||
|
||||
* property_field - exposing @property like function as field in Model.dict()
|
||||
* predefined signals decorators (pre/post + save/update/delete)
|
||||
|
||||
"""
|
||||
from ormar.decorators.property_field import property_field
|
||||
|
||||
from ormar.decorators.signals import (
|
||||
post_bulk_update,
|
||||
post_delete,
|
||||
@ -23,7 +22,6 @@ from ormar.decorators.signals import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"property_field",
|
||||
"post_bulk_update",
|
||||
"post_delete",
|
||||
"post_save",
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from typing import Union
|
||||
|
||||
from ormar.exceptions import ModelDefinitionError
|
||||
|
||||
|
||||
def property_field(func: Callable) -> Union[property, Callable]:
|
||||
"""
|
||||
Decorator to set a property like function on Model to be exposed
|
||||
as field in dict() and fastapi response.
|
||||
Although you can decorate a @property field like this and this will work,
|
||||
mypy validation will complain about this.
|
||||
Note that "fields" exposed like this do not go through validation.
|
||||
|
||||
:raises ModelDefinitionError: if method has any other argument than self.
|
||||
:param func: decorated function to be exposed
|
||||
:type func: Callable
|
||||
:return: decorated function passed in func param, with set __property_field__ = True
|
||||
:rtype: Union[property, Callable]
|
||||
"""
|
||||
if isinstance(func, property): # pragma: no cover
|
||||
func.fget.__property_field__ = True
|
||||
else:
|
||||
arguments = list(inspect.signature(func).parameters.keys())
|
||||
if len(arguments) > 1 or arguments[0] != "self":
|
||||
raise ModelDefinitionError(
|
||||
"property_field decorator can be used "
|
||||
"only on methods with no arguments"
|
||||
)
|
||||
func.__dict__["__property_field__"] = True
|
||||
return func
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Callable, List, TYPE_CHECKING, Type, Union
|
||||
from typing import TYPE_CHECKING, Callable, List, Type, Union
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from ormar import Model
|
||||
@ -34,7 +34,7 @@ def receiver(
|
||||
else:
|
||||
_senders = senders
|
||||
for sender in _senders:
|
||||
signals = getattr(sender.Meta.signals, signal)
|
||||
signals = getattr(sender.ormar_config.signals, signal)
|
||||
signals.connect(func)
|
||||
return func
|
||||
|
||||
|
||||
@ -15,11 +15,9 @@ class ModelDefinitionError(AsyncOrmException):
|
||||
"""
|
||||
Raised for errors related to the model definition itself:
|
||||
|
||||
* setting @property_field on method with arguments other than func(self)
|
||||
* defining a Field without required parameters
|
||||
* defining a model with more than one primary_key
|
||||
* defining a model without primary_key
|
||||
* setting primary_key column as pydantic_only
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user