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:
collerek
2024-03-23 19:28:28 +01:00
committed by GitHub
parent 3a206dd8dc
commit 500625f0ec
294 changed files with 8132 additions and 9311 deletions

View File

@ -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

View File

@ -27,6 +27,6 @@ jobs:
run: | run: |
echo $RELEASE_VERSION echo $RELEASE_VERSION
echo ${{ env.RELEASE_VERSION }} echo ${{ env.RELEASE_VERSION }}
# - name: Deploy - name: Deploy
# run: | run: |
# mike deploy --push --update-aliases ${{ env.RELEASE_VERSION }} latest mike deploy --push --update-aliases ${{ env.RELEASE_VERSION }} latest

44
.github/workflows/lint.yml vendored Normal file
View 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

View File

@ -1,14 +1,14 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python # 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 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: build name: test
on: on:
push: push:
branches-ignore: branches-ignore:
- 'gh-pages' - 'gh-pages'
pull_request: pull_request:
branches: [ master ] branches: [ master, pydantic_v2 ]
jobs: jobs:
tests: tests:
@ -17,7 +17,7 @@ jobs:
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/ormar' if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/ormar'
strategy: strategy:
matrix: 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 fail-fast: false
services: services:
mysql: mysql:
@ -39,35 +39,75 @@ jobs:
POSTGRES_DB: testsuite POSTGRES_DB: testsuite
ports: ports:
- 5432:5432 - 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: steps:
- uses: actions/checkout@v3 - name: Checkout
uses: actions/checkout@v3
with:
submodules: false
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} 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: | run: |
python -m pip install poetry==1.4.2 poetry --version
poetry install --extras "all" poetry config --list
env:
POETRY_VIRTUALENVS_CREATE: false - name: Install dependencies
run: poetry install --extras "all"
- name: Run mysql - name: Run mysql
env: env:
DATABASE_URL: "mysql://username:password@127.0.0.1:3306/testsuite" DATABASE_URL: "mysql://username:password@127.0.0.1:3306/testsuite"
run: bash scripts/test.sh 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 - name: Run postgres
env: env:
DATABASE_URL: "postgresql://username:password@localhost:5432/testsuite" DATABASE_URL: "postgresql://username:password@localhost:5432/testsuite"
run: bash scripts/test.sh run: bash scripts/test.sh
- name: Run sqlite - name: Run sqlite
env: env:
DATABASE_URL: "sqlite:///testsuite" DATABASE_URL: "sqlite:///testsuite"
run: bash scripts/test.sh run: bash scripts/test.sh
- run: mypy ormar tests benchmarks
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3.1.6 uses: codecov/codecov-action@v3.1.6
- name: Test & publish code coverage - name: Test & publish code coverage
uses: paambaati/codeclimate-action@v5.0.0 uses: paambaati/codeclimate-action@v5.0.0
if: github.event.pull_request.head.repo.full_name == 'collerek/ormar' if: github.event.pull_request.head.repo.full_name == 'collerek/ormar'

31
.github/workflows/test_docs.yml vendored Normal file
View 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
View 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

View File

@ -1,31 +1,9 @@
repos: repos:
- repo: https://github.com/psf/black - repo: local
rev: 22.3.0
hooks: hooks:
- id: black - id: pre-commit-local
exclude: ^(docs_src/|examples/) name: format
- repo: https://github.com/pycqa/flake8 entry: make pre-commit
rev: 3.9.2 language: python
hooks: pass_filenames: false
- 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
]

View File

@ -15,18 +15,22 @@ test_mysql:
test_sqlite: test_sqlite:
bash scripts/test.sh -svv bash scripts/test.sh -svv
test_docs:
bash scripts/test_docs.sh -svv
test: test:
pytest pytest -svv tests/
coverage: 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: type_check:
black ormar tests mkdir -p .mypy_cache && poetry run python -m mypy ormar tests --ignore-missing-imports --install-types --non-interactive
lint: lint:
black ormar tests poetry run python -m ruff . --fix
flake8 ormar
mypy: fmt:
mypy ormar tests poetry run python -m black .
pre-commit: fmt lint type_check

View File

@ -171,39 +171,31 @@ import ormar
import sqlalchemy import sqlalchemy
DATABASE_URL = "sqlite:///db.sqlite" DATABASE_URL = "sqlite:///db.sqlite"
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
engine=sqlalchemy.create_engine(DATABASE_URL),
# 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
# Note that all type hints are optional # Note that all type hints are optional
# below is a perfectly valid model declaration # below is a perfectly valid model declaration
# class Author(ormar.Model): # class Author(ormar.Model):
# class Meta(BaseMeta): # ormar_config = base_ormar_config.copy(tablename="authors")
# tablename = "authors"
# #
# id = ormar.Integer(primary_key=True) # <= notice no field types # id = ormar.Integer(primary_key=True) # <= notice no field types
# name = ormar.String(max_length=100) # name = ormar.String(max_length=100)
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="authors")
tablename = "authors"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="books")
tablename = "books"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Optional[Author] = ormar.ForeignKey(Author) author: Optional[Author] = ormar.ForeignKey(Author)
@ -214,10 +206,9 @@ class Book(ormar.Model):
# create the database # create the database
# note that in production you should use migrations # note that in production you should use migrations
# note that this is not required if you connect to existing database # 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 # just to be sure we clear the db before
metadata.drop_all(engine) base_ormar_config.metadata.drop_all(base_ormar_config.engine)
metadata.create_all(engine) base_ormar_config.metadata.create_all(base_ormar_config.engine)
# all functions below are divided into functionality categories # all functions below are divided into functionality categories
@ -546,7 +537,7 @@ async def raw_data():
async def with_connect(function): async def with_connect(function):
# note that for any other backend than sqlite you actually need to # note that for any other backend than sqlite you actually need to
# connect to the database to perform db operations # connect to the database to perform db operations
async with database: async with base_ormar_config.database:
await function() await function()
# note that if you use framework like `fastapi` you shouldn't connect # note that if you use framework like `fastapi` you shouldn't connect
@ -576,7 +567,7 @@ for func in [
asyncio.run(with_connect(func)) asyncio.run(with_connect(func))
# drop the database tables # drop the database tables
metadata.drop_all(engine) base_ormar_config.metadata.drop_all(base_ormar_config.engine)
``` ```
## Ormar Specification ## Ormar Specification
@ -654,7 +645,6 @@ The following keyword arguments are supported on all field types.
* `unique: bool` * `unique: bool`
* `choices: typing.Sequence` * `choices: typing.Sequence`
* `name: str` * `name: str`
* `pydantic_only: bool`
All fields are required unless one of the following is set: 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** * `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. * `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. 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 ### Available signals

View File

@ -3,31 +3,20 @@ import random
import string import string
import time import time
import databases
import nest_asyncio import nest_asyncio
import ormar
import pytest import pytest
import pytest_asyncio import pytest_asyncio
import sqlalchemy from tests.lifespan import init_tests
from tests.settings import create_config
import ormar
from tests.settings import DATABASE_URL
base_ormar_config = create_config()
nest_asyncio.apply() nest_asyncio.apply()
database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
pytestmark = pytest.mark.asyncio pytestmark = pytest.mark.asyncio
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="authors")
tablename = "authors"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -41,8 +30,7 @@ class AuthorWithManyFields(Author):
class Publisher(ormar.Model): class Publisher(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="publishers")
tablename = "publishers"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -50,8 +38,7 @@ class Publisher(ormar.Model):
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="books")
tablename = "books"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Author = ormar.ForeignKey(Author, index=True) author: Author = ormar.ForeignKey(Author, index=True)
@ -60,13 +47,7 @@ class Book(ormar.Model):
year: int = ormar.Integer(nullable=True) year: int = ormar.Integer(nullable=True)
@pytest.fixture(autouse=True, scope="function") # TODO: fix this to be module create_test_database = init_tests(base_ormar_config, scope="function")
def create_test_database():
engine = sqlalchemy.create_engine(DATABASE_URL)
metadata.drop_all(engine)
metadata.create_all(engine)
yield
metadata.drop_all(engine)
@pytest_asyncio.fixture @pytest_asyncio.fixture
@ -86,7 +67,7 @@ async def authors_in_db(num_models: int):
authors = [ authors = [
Author( Author(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
for i in range(0, num_models) for i in range(0, num_models)
] ]

View File

@ -15,7 +15,7 @@ async def test_making_and_inserting_models_in_bulk(aio_benchmark, num_models: in
authors = [ authors = [
Author( Author(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
for i in range(0, num_models) for i in range(0, num_models)
] ]

View File

@ -16,7 +16,7 @@ async def test_creating_models_individually(aio_benchmark, num_models: int):
for idx in range(0, num_models): for idx in range(0, num_models):
author = await Author.objects.create( author = await Author.objects.create(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
authors.append(author) authors.append(author)
return authors 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): for idx in range(0, num_models):
author, created = await Author.objects.get_or_create( author, created = await Author.objects.get_or_create(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
assert created assert created
authors.append(author) 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): for idx in range(0, num_models):
author = await Author.objects.update_or_create( author = await Author.objects.update_or_create(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
authors.append(author) authors.append(author)
return authors return authors

View File

@ -15,13 +15,13 @@ async def test_initializing_models(aio_benchmark, num_models: int):
authors = [ authors = [
Author( Author(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
for i in range(0, num_models) for i in range(0, num_models)
] ]
assert len(authors) == num_models assert len(authors) == num_models
initialize_models(num_models) _ = initialize_models(num_models)
@pytest.mark.parametrize("num_models", [10, 20, 40]) @pytest.mark.parametrize("num_models", [10, 20, 40])

View File

@ -15,7 +15,7 @@ async def test_saving_models_individually(aio_benchmark, num_models: int):
authors = [ authors = [
Author( Author(
name="".join(random.sample(string.ascii_letters, 5)), name="".join(random.sample(string.ascii_letters, 5)),
score=random.random() * 100, score=int(random.random() * 100),
) )
for i in range(0, num_models) for i in range(0, num_models)
] ]

View File

@ -26,7 +26,8 @@ Here you can find a very simple sample application code.
### Imports and initialization ### 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 ```python
from typing import List, Optional from typing import List, Optional
@ -36,29 +37,26 @@ from fastapi import FastAPI
import ormar import ormar
app = FastAPI() from contextlib import asynccontextmanager
metadata = sqlalchemy.MetaData() from fastapi import FastAPI
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()
@app.on_event("shutdown") @asynccontextmanager
async def shutdown() -> None: async def lifespan(_: FastAPI) -> AsyncIterator[None]:
database_ = app.state.database if not config.database.is_connected:
if database_.is_connected: await config.database.connect()
await database_.disconnect()
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 !!!info
@ -71,21 +69,21 @@ Define ormar models with appropriate fields.
Those models will be used instead of pydantic ones. Those models will be used instead of pydantic ones.
```python ```python
class Category(ormar.Model): base_ormar_config = OrmarConfig(
class Meta:
tablename = "categories"
metadata = metadata metadata = metadata
database = database database = database
)
class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -122,7 +120,7 @@ async def create_category(category: Category):
@app.put("/items/{item_id}") @app.put("/items/{item_id}")
async def get_item(item_id: int, item: Item): async def get_item(item_id: int, item: Item):
item_db = await Item.objects.get(pk=item_id) 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}") @app.delete("/items/{item_id}")
@ -197,14 +195,14 @@ def test_all_endpoints():
assert items[0] == item assert items[0] == item
item.name = "New name" item.name = "New name"
response = client.put(f"/items/{item.pk}", json=item.dict()) response = client.put(f"/items/{item.pk}", json=item.model_dump())
assert response.json() == item.dict() assert response.json() == item.model_dump()
response = client.get("/items/") response = client.get("/items/")
items = [Item(**item) for item in response.json()] items = [Item(**item) for item in response.json()]
assert items[0].name == "New name" 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__" assert response.json().get("deleted_rows", "__UNDEFINED__") != "__UNDEFINED__"
response = client.get("/items/") response = client.get("/items/")
items = response.json() items = response.json()

View File

@ -23,11 +23,13 @@ Field is not required if (any/many/all) of following:
Example: Example:
```python ```python
base_ormar_config = ormar.OrmarConfig(
metadata=metadata
database=database
)
class User(ormar.Model): class User(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename: str = "users"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
email: str = ormar.String(max_length=255) email: str = ormar.String(max_length=255)
@ -66,14 +68,14 @@ RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority
@app.post("/users3/", response_model=User) # here you can also use both ormar/pydantic @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 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 # 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 !!!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 !!!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).
@ -94,7 +96,7 @@ RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority
@app.post("/users3/", response_model=User) @app.post("/users3/", response_model=User)
async def create_user3(user: RequestUser): # type: ignore async def create_user3(user: RequestUser): # type: ignore
# note how now user is not ormar Model so you need to convert # 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. 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"}}) RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
# do not use the app decorator # do not use the app decorator
async def create_user3(user: User): # use ormar model here 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 # overwrite the function annotations entry for user param with generated model
create_user3.__annotations__["user"] = RequestUser create_user3.__annotations__["user"] = RequestUser
# manually call app functions (app.get, app.post etc.) and pass your function reference # manually call app functions (app.get, app.post etc.) and pass your function reference
@ -126,8 +128,7 @@ Sample:
import pydantic import pydantic
class UserCreate(pydantic.BaseModel): class UserCreate(pydantic.BaseModel):
class Config: model_config = pydantic.ConfigDict(from_attributes=True)
orm_mode = True
email: str email: str
first_name: str first_name: str
@ -139,5 +140,5 @@ class UserCreate(pydantic.BaseModel):
async def create_user3(user: UserCreate): # use pydantic model here async def create_user3(user: UserCreate): # use pydantic model here
# note how now request param is a pydantic model and not the ormar one # 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 # 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()
``` ```

View File

@ -22,11 +22,13 @@ Field is not required if (any/many/all) of following:
Example: Example:
```python ```python
base_ormar_config = ormar.OrmarConfig(
metadata=metadata
database=database
)
class User(ormar.Model): class User(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename: str = "users"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
email: str = ormar.String(max_length=255) email: str = ormar.String(max_length=255)
@ -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: Assume for a second that our user's category is a separate model:
```python ```python
class BaseMeta(ormar.ModelMeta): base_ormar_config = ormar.OrmarConfig(
metadata = metadata metadata=metadata
database = database database=database
)
class Category(ormar.Model): class Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename: str = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=255) name: str = ormar.String(max_length=255)
@ -125,8 +127,7 @@ class Category(ormar.Model):
class User(ormar.Model): class User(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename: str = "users"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
email: str = ormar.String(max_length=255) email: str = ormar.String(max_length=255)
@ -153,7 +154,7 @@ In example `response_model_exclude={"category__priority", "category__other_field
Note that apart from `response_model_exclude` parameter `fastapi` supports also other parameters inherited from `pydantic`. 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. 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 . Alternatively you can just return a dict from `ormar.Model` and use .
@ -166,14 +167,14 @@ Like this you can also set exclude/include as dict and exclude fields on nested
@app.post("/users2/", response_model=User) @app.post("/users2/", response_model=User)
async def create_user2(user: User): async def create_user2(user: User):
user = await user.save() user = await user.save()
return user.dict(exclude={'password'}) return user.model_dump(exclude={'password'})
# could be also something like return user.dict(exclude={'category': {'priority'}}) to exclude category priority # could be also something like return user.model_dump(exclude={'category': {'priority'}}) to exclude category priority
``` ```
!!!Note !!!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. 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. Like this you can also set exclude/include as dict and exclude fields on nested models.
@ -187,7 +188,7 @@ So if you skip `response_model` altogether you can do something like this:
@app.post("/users4/") # note no response_model @app.post("/users4/") # note no response_model
async def create_user4(user: User): async def create_user4(user: User):
user = await user.save() user = await user.save()
return user.dict(exclude={'last_name'}) return user.model_dump(exclude={'last_name'})
``` ```
!!!Note !!!Note
@ -213,7 +214,7 @@ async def create_user3(user: User):
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 !!!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).
@ -229,8 +230,7 @@ Sample:
import pydantic import pydantic
class UserBase(pydantic.BaseModel): class UserBase(pydantic.BaseModel):
class Config: model_config = pydantic.ConfigDict(from_attributes=True)
orm_mode = True
email: str email: str
first_name: str first_name: str

View File

@ -29,7 +29,6 @@ Automatically changed to True if user provide one of the following:
* `default` value or function is provided * `default` value or function is provided
* `server_default` value or function is provided * `server_default` value or function is provided
* `autoincrement` is set on `Integer` `primary_key` field * `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. Specifies if field is optional or required, used both with sql and pydantic.
@ -109,7 +108,7 @@ Used in sql only.
Sample usage: Sample usage:
```Python hl_lines="21-23" ```Python hl_lines="20-22"
--8<-- "../docs_src/fields/docs004.py" --8<-- "../docs_src/fields/docs004.py"
``` ```
@ -167,20 +166,6 @@ Sets the unique constraint on a table's column.
Used in sql only. 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 ## overwrite_pydantic_type
By default, ormar uses predefined pydantic field types that it applies on model creation (hence the type hints are optional). By default, ormar uses predefined pydantic field types that it applies on model creation (hence the type hints are optional).
@ -199,12 +184,15 @@ So it's on you as a user to provide a type that is valid in the context of given
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 ```python
base_ormar_config = ormar.OrmarConfig(
metadata=metadata
database=database
)
# sample overwrites # sample overwrites
class OverwriteTest(ormar.Model): class OverwriteTest(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="overwrites")
tablename = "overwrites"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
my_int: str = ormar.Integer(overwrite_pydantic_type=PositiveInt) 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]]]) 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 [relations]: ../relations/index.md
[queries]: ../queries/index.md [queries]: ../queries/index.md
[pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types [pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types

View File

@ -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. 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 Filter(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "filters"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, 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 ```python
class Hash(ormar.Model): class Hash(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="hashes")
tablename = "hashes"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=128, name: str = ormar.String(max_length=128,
@ -106,8 +109,7 @@ as the returned value is parsed to corresponding python type.
```python ```python
class Filter(ormar.Model): class Filter(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "filters"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, name: str = ormar.String(max_length=100,
@ -152,8 +154,7 @@ argument by `encrypt_custom_backend`.
```python ```python
class Filter(ormar.Model): class Filter(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "filters"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, name: str = ormar.String(max_length=100,

View File

@ -1,10 +1,10 @@
# Fields # 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 !!!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. 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 ```python
import base64 import base64
... # other imports skipped for brevity ... # other imports skipped for brevity
base_ormar_config = ormar.OrmarConfig(
metadata=metadata
database=database
)
class LargeBinaryStr(ormar.Model): class LargeBinaryStr(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="my_str_blobs")
tablename = "my_str_blobs"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
test_binary: str = ormar.LargeBinary( 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` * Sqlalchemy column: `sqlalchemy.Enum`
* Type (used for pydantic): `Type[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 [relations]: ../relations/index.md
[queries]: ../queries.md [queries]: ../queries.md

View File

@ -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. exactly what's going to happen during loading from database.
```python ```python
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() metadata=sqlalchemy.MetaData(),
database=databases.Database(DATABASE_URL),
)
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
class ModelTest(ormar.Model): class ModelTest(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=200) 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`. Note that setting a default to `None` is the same as setting the field to `Optional`.
```python ```python
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() metadata=sqlalchemy.MetaData(),
database=databases.Database(DATABASE_URL),
)
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
class ModelTest(ormar.Model): class ModelTest(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=200) name: str = ormar.String(max_length=200)
@ -97,13 +91,12 @@ on initialization and each database load.
from pydantic import Field, PaymentCardNumber 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 = [ CARD_NUMBERS = [
"123456789007", "123456789007",
@ -119,8 +112,7 @@ def get_number():
class ModelTest2(ormar.Model): class ModelTest2(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=200) 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 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): class PydanticTest(BaseModel):
aa: str aa: str
@ -163,8 +154,7 @@ class PydanticTest(BaseModel):
class ModelTest3(ormar.Model): class ModelTest3(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
# provide your custom init function # provide your custom init function
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -21,7 +21,7 @@
### Overview ### 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**. MySQL**, and **SQLite**.
The main benefits of using `ormar` are: 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 ### Part of the `fastapi` ecosystem
As part of the fastapi ecosystem `ormar` is supported in libraries that somehow work with databases. As part of the fastapi ecosystem `ormar` is supported in selected 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)
Ormar remains sql dialect agnostic - so only columns working in all supported backends are implemented. 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. * [`sqlalchemy core`][sqlalchemy-core] for query building.
* [`databases`][databases] for cross-database async support. * [`databases`][databases] for cross-database async support.
* [`pydantic`][pydantic] for data validation. * [`pydantic`][pydantic] for data validation.
* `typing_extensions` for python 3.6 - 3.7
### License ### License
@ -128,17 +121,20 @@ For tests and basic applications the `sqlalchemy` is more than enough:
# 1. Imports # 1. Imports
import sqlalchemy import sqlalchemy
import databases import databases
import ormar
# 2. Initialization # 2. Initialization
DATABASE_URL = "sqlite:///db.sqlite" DATABASE_URL = "sqlite:///db.sqlite"
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() metadata=sqlalchemy.MetaData(),
database=databases.Database(DATABASE_URL),
engine=sqlalchemy.create_engine(DATABASE_URL),
)
# Define models here # Define models here
# 3. Database creation and tables creation # 3. Database creation and tables creation
engine = sqlalchemy.create_engine(DATABASE_URL) base_ormar_config.metadata.create_all(engine)
metadata.create_all(engine)
``` ```
For a sample configuration of alembic and more information regarding migrations and 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 from typing import Optional
import databases import databases
import pydantic
import ormar import ormar
import sqlalchemy import sqlalchemy
DATABASE_URL = "sqlite:///db.sqlite" DATABASE_URL = "sqlite:///db.sqlite"
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() 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 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
# 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 # Note that all type hints are optional
# below is a perfectly valid model declaration # below is a perfectly valid model declaration
# class Author(ormar.Model): # class Author(ormar.Model):
# class Meta(BaseMeta): # ormar_config = base_ormar_config.copy(tablename="authors")
# tablename = "authors"
# #
# id = ormar.Integer(primary_key=True) # <= notice no field types # id = ormar.Integer(primary_key=True) # <= notice no field types
# name = ormar.String(max_length=100) # name = ormar.String(max_length=100)
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="authors")
tablename = "authors"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="books")
tablename = "books"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Optional[Author] = ormar.ForeignKey(Author) author: Optional[Author] = ormar.ForeignKey(Author)
@ -224,10 +214,9 @@ class Book(ormar.Model):
# create the database # create the database
# note that in production you should use migrations # note that in production you should use migrations
# note that this is not required if you connect to existing database # 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 # just to be sure we clear the db before
metadata.drop_all(engine) base_ormar_config.metadata.drop_all(engine)
metadata.create_all(engine) base_ormar_config.metadata.create_all(engine)
# all functions below are divided into functionality categories # 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` * `server_default: Any`
* `index: bool` * `index: bool`
* `unique: bool` * `unique: bool`
* `choices: typing.Sequence`
* `name: str` * `name: str`
* `pydantic_only: bool`
All fields are required unless one of the following is set: 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** * `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. * `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. 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 ### Available signals

View File

@ -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. All three should install along the installation of ormar if not present at your system before.
* databases * databases
* pydantic>=1.5 * pydantic
* sqlalchemy * sqlalchemy
The required versions are pinned in the pyproject.toml file.
## Optional dependencies ## Optional dependencies
*ormar* has three optional dependencies based on database backend you use: *ormar* has three optional dependencies based on database backend you use:
### Postgresql ### Database backend
#### Postgresql
```py ```py
pip install ormar[postgresql] pip install ormar[postgresql]
``` ```
Will install also `asyncpg` and `psycopg2`. Will install also `asyncpg` and `psycopg2`.
### Mysql #### Mysql
```py ```py
pip install ormar[mysql] pip install ormar[mysql]
@ -36,7 +39,7 @@ pip install ormar[mysql]
Will install also `aiomysql` and `pymysql`. Will install also `aiomysql` and `pymysql`.
### Sqlite #### Sqlite
```py ```py
pip install ormar[sqlite] pip install ormar[sqlite]

330
docs/migration.md Normal file
View 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.

View File

@ -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. 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" --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. Only one primary key column is allowed.
```Python hl_lines="15 16 17" ```Python hl_lines="15-17"
--8<-- "../docs_src/models/docs001.py" --8<-- "../docs_src/models/docs001.py"
``` ```
@ -42,15 +42,15 @@ id: int = ormar.Integer(primary_key=True, autoincrement=False)
#### Non Database Fields #### 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 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, 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`, 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). 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 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. (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`.). you should get back exactly same value in `response`.).
!!!warning !!!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 ```Python hl_lines="19"
`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"
--8<-- "../docs_src/models/docs014.py" --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. 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
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. response with `include`/`exclude` and `response_model_include`/`response_model_exclude` accordingly.
```python ```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 User(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="users2")
tablename: str = "users2"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
email: str = ormar.String(max_length=255, nullable=False) 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) first_name: str = ormar.String(max_length=255)
last_name: str = ormar.String(max_length=255) last_name: str = ormar.String(max_length=255)
category: str = ormar.String(max_length=255, nullable=True) category: str = ormar.String(max_length=255, nullable=True)
timestamp: datetime.datetime = ormar.DateTime( timestamp: datetime.datetime = pydantic.Field(
pydantic_only=True, default=datetime.datetime.now default=datetime.datetime.now
) )
# <==related of code removed for clarity==> # <==part of related code removed for clarity==>
app =FastAPI() app = FastAPI()
@app.post("/users/") @app.post("/users/")
async def create_user(user: User): async def create_user(user: User):
return await user.save() return await user.save()
# <==related of code removed for clarity==> # <==part of related code removed for clarity==>
def test_excluding_fields_in_endpoints(): def test_excluding_fields_in_endpoints():
client = TestClient(app) client = TestClient(app)
@ -127,121 +129,7 @@ def test_excluding_fields_in_endpoints():
assert response.json().get("timestamp") == str(timestamp).replace(" ", "T") assert response.json().get("timestamp") == str(timestamp).replace(" ", "T")
# <==related of code removed for clarity==> # <==part of related 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==>
``` ```
#### Fields names vs Column names #### 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 with specifying `name` parameter during Field declaration
Here you have a sample model with changed names 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" --8<-- "../docs_src/models/docs008.py"
``` ```
Note that you can also change the ForeignKey column name Note that you can also change the ForeignKey column name
```Python hl_lines="21" ```Python hl_lines="34"
--8<-- "../docs_src/models/docs009.py" --8<-- "../docs_src/models/docs009.py"
``` ```
But for now you cannot change the ManyToMany column names as they go through other Model anyway. But for now you cannot change the ManyToMany column names as they go through other Model anyway.
```Python hl_lines="28" ```Python hl_lines="43"
--8<-- "../docs_src/models/docs010.py" --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` 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 ```python
import ormar import ormar
@ -288,12 +176,10 @@ class MyQuerySetClass(QuerySet):
class Book(ormar.Model): class Book(ormar.Model):
ormar_config = base_ormar_config.copy(
class Meta(ormar.ModelMeta): queryset_class=MyQuerySetClass,
metadata = metadata tablename="book",
database = database )
tablename = "book"
queryset_class = MyQuerySetClass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=32) 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. Note that for better IDE support and mypy checks you can provide type hints.
```Python hl_lines="15-17"
--8<-- "../docs_src/models/docs011.py"
```
But that didn't play well with static type checkers like `mypy` and `pydantic` PyCharm plugin.
Therefore from version >=0.4.0 `ormar` switched to new notation.
```Python hl_lines="15-17" ```Python hl_lines="15-17"
--8<-- "../docs_src/models/docs001.py" --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. 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" --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. 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" --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 #### 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="9-12 19 28"
```Python hl_lines="14 20 33"
--8<-- "../docs_src/models/docs013.py" --8<-- "../docs_src/models/docs013.py"
``` ```
!!!warning
You need to subclass your `MainMeta` class in each `Model` class as those classes store configuration variables
that otherwise would be overwritten by each `Model`.
### Table Names ### Table Names
By default table name is created from Model class name as lowercase name plus 's'. By default table name is created from Model class name as lowercase name plus 's'.
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" --8<-- "../docs_src/models/docs002.py"
``` ```
@ -395,7 +270,7 @@ 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. 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
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.
@ -405,9 +280,9 @@ Right now only `IndexColumns` and `UniqueColumns` constraints are supported.
#### UniqueColumns #### 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" --8<-- "../docs_src/models/docs006.py"
``` ```
@ -418,9 +293,9 @@ You can set this parameter by providing `Meta` class `constraints` argument.
#### IndexColumns #### 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" --8<-- "../docs_src/models/docs017.py"
``` ```
@ -431,23 +306,23 @@ You can set this parameter by providing `Meta` class `constraints` argument.
#### CheckColumns #### 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" --8<-- "../docs_src/models/docs018.py"
``` ```
!!!note !!!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 ### Pydantic configuration
As each `ormar.Model` is also a `pydantic` model, you might want to tweak the settings of the 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 !!!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.
@ -456,13 +331,11 @@ Note that if you do not provide your own configuration, ormar will do it for you
The default config provided is as follows: The default config provided is as follows:
```python ```python
class Config(pydantic.BaseConfig): model_config = ConfigDict(validate_assignment=True, ser_json_bytes="base64")
orm_mode = True
validate_assignment = True
``` ```
So to overwrite setting or provide your own a sample model can look like following: 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" --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. 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`. 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) Note that `ormar` does not allow accepting extra fields, you can only ignore them or forbid them (raise exception if present)
```python ```python
from ormar import Extra from ormar import Extra, OrmarConfig
class Child(ormar.Model): class Child(ormar.Model):
class Meta(ormar.ModelMeta): ormar_config = OrmarConfig(
tablename = "children" tablename="children",
metadata = metadata extra=Extra.ignore # set extra setting to prevent exceptions on extra fields presence
database = database )
extra = Extra.ignore # set extra setting to prevent exceptions on extra fields presence
id: int = ormar.Integer(name="child_id", primary_key=True) id: int = ormar.Integer(name="child_id", primary_key=True)
first_name: str = ormar.String(name="fname", max_length=100) first_name: str = ormar.String(name="fname", max_length=100)
last_name: str = ormar.String(name="lname", 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 ## Model sort order
When querying the database with given model by default the Model is ordered by the `primary_key` 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` 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 ```python
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
# default sort by column id ascending # default sort by column id ascending
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(
tablename = "authors" tablename="authors",
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
``` ```
Modified Modified
```python ```python hl_lines="9"
base_ormar_config = ormar.OrmarConfig(
database = databases.Database(DATABASE_URL) database=databases.Database(DATABASE_URL),
metadata = sqlalchemy.MetaData() metadata=sqlalchemy.MetaData(),
)
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
# now default sort by name descending # now default sort by name descending
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(
tablename = "authors" orders_by = ["-name"],
orders_by = ["-name"] tablename="authors",
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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. 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. 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" --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 Each model has a `QuerySet` initialised as `objects` parameter
```Python hl_lines="23" ```Python hl_lines="28"
--8<-- "../docs_src/models/docs007.py" --8<-- "../docs_src/models/docs007.py"
``` ```

View File

@ -7,7 +7,7 @@ Out of various types of ORM models inheritance `ormar` currently supports two of
## Types of inheritance ## 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 * **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), 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. defining `ormar.Fields` as class variables.
```python ```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 # a mixin defines the fields but is a normal python class
class AuditMixin: class AuditMixin:
created_by: str = ormar.String(max_length=100) created_by: str = ormar.String(max_length=100)
@ -45,10 +52,7 @@ class DateFieldsMixins:
# a models can inherit from one or more mixins # a models can inherit from one or more mixins
class Category(ormar.Model, DateFieldsMixins, AuditMixin): class Category(ormar.Model, DateFieldsMixins, AuditMixin):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50, unique=True, index=True) name: str = ormar.String(max_length=50, unique=True, index=True)
@ -57,7 +61,7 @@ class Category(ormar.Model, DateFieldsMixins, AuditMixin):
!!!tip !!!tip
Note that Mixins are **not** models, so you still need to inherit 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`, A Category class above will have four additional fields: `created_date`, `updated_date`,
`created_by` and `updated_by`. `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. actual `ormar.Models` as base classes.
!!!warning !!!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. to inherit from non abstract marked class `ModelDefinitionError` will be raised.
Since this abstract Model will never be initialized you can skip `metadata` 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 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 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 # note that base classes have abstract=True
# since this model will never be initialized you can skip metadata and database # since this model will never be initialized you can skip metadata and database
class AuditModel(ormar.Model): class AuditModel(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
created_by: str = ormar.String(max_length=100) created_by: str = ormar.String(max_length=100)
updated_by: str = ormar.String(max_length=100, default="Sam") 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 # but if you provide it it will be inherited - DRY (Don't Repeat Yourself) in action
class DateFieldsModel(ormar.Model): class DateFieldsModel(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(
abstract = True abstract=True,
metadata = metadata metadata=metadata,
database = db database=db,
)
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now) created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
updated_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 # that way you do not have to provide metadata and databases in concrete class
class Category(DateFieldsModel, AuditModel): class Category(DateFieldsModel, AuditModel):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50, unique=True, index=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` The list of inherited options/settings is as follows: `metadata`, `database`
and `constraints`. 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 Of course apart from that all fields from base classes are combined and created in the
concrete table of the final Model. 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 Whenever you define a field with same name and new definition it will completely replace
the previously defined one. the previously defined one.
```python ```python hl_lines="28"
# base class # base class
class DateFieldsModel(ormar.Model): class DateFieldsModel(ormar.Model):
class Meta: ormar_config = OrmarConfig(
abstract = True abstract=True,
metadata = metadata metadata=metadata,
database = db database=db,
# note that UniqueColumns need sqlalchemy db columns names not the ormar ones # 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( created_date: datetime.datetime = ormar.DateTime(
default=datetime.datetime.now, name="creation_date" default=datetime.datetime.now, name="creation_date"
@ -159,10 +161,11 @@ class DateFieldsModel(ormar.Model):
class RedefinedField(DateFieldsModel): class RedefinedField(DateFieldsModel):
class Meta(ormar.ModelMeta): ormar_config = OrmarConfig(
tablename = "redefines" tablename="redefines",
metadata = metadata metadata=metadata,
database = db database=db,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
# here the created_date is replaced by the String field # 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 # 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.default is None
assert changed_field.alias == "creation_date" 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( assert isinstance(
RedefinedField.Meta.table.columns["creation_date"].type, RedefinedField.ormar_config.table.columns["creation_date"].type,
sqlalchemy.sql.sqltypes.String, sqlalchemy.sql.sqltypes.String,
) )
``` ```
@ -225,9 +228,7 @@ That might sound complicated but let's look at the following example:
```python ```python
# normal model used in relation # normal model used in relation
class Person(ormar.Model): class Person(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -235,10 +236,7 @@ class Person(ormar.Model):
# parent model - needs to be abstract # parent model - needs to be abstract
class Car(ormar.Model): class Car(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50) name: str = ormar.String(max_length=50)
@ -249,16 +247,13 @@ class Car(ormar.Model):
class Truck(Car): class Truck(Car):
class Meta: ormar_config = base_ormar_config.copy()
pass
max_capacity: int = ormar.Integer() max_capacity: int = ormar.Integer()
class Bus(Car): class Bus(Car):
class Meta: ormar_config = base_ormar_config.copy(tablename="buses")
# default naming is name.lower()+'s' so it's ugly for buss ;)
tablename = "buses"
max_persons: int = ormar.Integer() 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: Now when you will inspect the fields on Person model you will get:
```python ```python
Person.Meta.model_fields Person.ormar_config.model_fields
""" """
{'id': <class 'ormar.fields.model_fields.Integer'>, {'id': <class 'ormar.fields.model_fields.Integer'>,
'name': <class 'ormar.fields.model_fields.String'>, 'name': <class 'ormar.fields.model_fields.String'>,
@ -293,8 +288,7 @@ different `related_name` parameter.
```python ```python
# rest of the above example remains the same # rest of the above example remains the same
class Bus(Car): class Bus(Car):
class Meta: ormar_config = base_ormar_config.copy(tablename="buses")
tablename = "buses"
# new field that changes the related_name # new field that changes the related_name
owner: Person = ormar.ForeignKey(Person, related_name="buses") owner: Person = ormar.ForeignKey(Person, related_name="buses")
@ -304,7 +298,7 @@ class Bus(Car):
Now the columns looks much better. Now the columns looks much better.
```python ```python
Person.Meta.model_fields Person.ormar_config.model_fields
""" """
{'id': <class 'ormar.fields.model_fields.Integer'>, {'id': <class 'ormar.fields.model_fields.Integer'>,
'name': <class 'ormar.fields.model_fields.String'>, '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 Similarly, you can inherit from Models that have ManyToMany relations declared but
there is one, but substantial difference - the Through model. 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 (`from` and `to` ones), each child that inherits the m2m relation field has to have separate
Through model. Through model.
@ -344,27 +338,18 @@ We will modify the previous example described above to use m2m relation for co_o
```python ```python
# person remain the same as above # person remain the same as above
class Person(ormar.Model): class Person(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
# new through model between Person and Car2 # new through model between Person and Car2
class PersonsCar(ormar.Model): class PersonsCar(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="cars_x_persons")
tablename = "cars_x_persons"
metadata = metadata
database = db
# note how co_owners is now ManyToMany relation # note how co_owners is now ManyToMany relation
class Car2(ormar.Model): class Car2(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(abstract=True)
# parent class needs to be marked abstract
abstract = True
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50) name: str = ormar.String(max_length=50)
@ -379,16 +364,13 @@ class Car2(ormar.Model):
# child models define only additional Fields # child models define only additional Fields
class Truck2(Car2): class Truck2(Car2):
class Meta: ormar_config = base_ormar_config.copy(tablename="trucks2")
# note how you don't have to provide inherited Meta params
tablename = "trucks2"
max_capacity: int = ormar.Integer() max_capacity: int = ormar.Integer()
class Bus2(Car2): class Bus2(Car2):
class Meta: ormar_config = base_ormar_config.copy(tablename="buses2")
tablename = "buses2"
max_persons: int = ormar.Integer() 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. You can verify the names by inspecting the list of fields present on `Person` model.
```python ```python
Person.Meta.model_fields Person.ormar_config.model_fields
{ {
# note how all relation fields need to be unique on Person # note how all relation fields need to be unique on Person
# regardless if autogenerated or manually overwritten # 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. so let's examine the through models for both `Bus2` and `Truck2` models.
```python ```python
Bus2.Meta.model_fields['co_owners'].through Bus2.ormar_config.model_fields['co_owners'].through
<class 'abc.PersonsCarBus2'> <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' 'cars_x_persons_buses2'
Truck2.Meta.model_fields['co_owners'].through Truck2.ormar_config.model_fields['co_owners'].through
<class 'abc.PersonsCarTruck2'> <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' '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: Note that original model is not only not used, the table for this model is removed from metadata:
```python ```python
Bus2.Meta.metadata.tables.keys() Bus2.ormar_config.metadata.tables.keys()
dict_keys(['test_date_models', 'categories', 'subjects', 'persons', 'trucks', 'buses', dict_keys(['test_date_models', 'categories', 'subjects', 'persons', 'trucks', 'buses',
'cars_x_persons_trucks2', 'trucks2', 'cars_x_persons_buses2', 'buses2']) '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
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. 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 ```python
metadata = sa.MetaData() base_ormar_config = OrmarConfig(
db = databases.Database(DATABASE_URL) metadata=sa.MetaData(),
database=databases.Database(DATABASE_URL),
)
class AuditModel(ormar.Model): class AuditModel(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
created_by: str = ormar.String(max_length=100) created_by: str = ormar.String(max_length=100)
updated_by: str = ormar.String(max_length=100, default="Sam") updated_by: str = ormar.String(max_length=100, default="Sam")
class DateFieldsModel(ormar.Model): class DateFieldsModel(ormar.Model):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
metadata = metadata
database = db
created_date: datetime.datetime = ormar.DateTime( created_date: datetime.datetime = ormar.DateTime(
default=datetime.datetime.now, name="creation_date" default=datetime.datetime.now, name="creation_date"
@ -499,10 +479,11 @@ class DateFieldsModel(ormar.Model):
class Category(DateFieldsModel, AuditModel): class Category(DateFieldsModel, AuditModel):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(
tablename = "categories" tablename="categories",
# set fields that should be skipped # 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) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50, unique=True, index=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: The same effect can be achieved by splitting base classes like:
```python ```python
metadata = sa.MetaData() base_ormar_config = OrmarConfig(
db = databases.Database(DATABASE_URL) metadata=sa.MetaData(),
database=databases.Database(DATABASE_URL),
)
class AuditCreateModel(ormar.Model): class AuditCreateModel(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
created_by: str = ormar.String(max_length=100) created_by: str = ormar.String(max_length=100)
class AuditUpdateModel(ormar.Model): class AuditUpdateModel(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
updated_by: str = ormar.String(max_length=100, default="Sam") updated_by: str = ormar.String(max_length=100, default="Sam")
class CreateDateFieldsModel(ormar.Model): class CreateDateFieldsModel(ormar.Model):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
metadata = metadata
database = db
created_date: datetime.datetime = ormar.DateTime( created_date: datetime.datetime = ormar.DateTime(
default=datetime.datetime.now, name="creation_date" default=datetime.datetime.now, name="creation_date"
) )
class UpdateDateFieldsModel(ormar.Model): class UpdateDateFieldsModel(ormar.Model):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(abstract=True)
abstract = True
metadata = metadata
database = db
updated_date: datetime.datetime = ormar.DateTime( updated_date: datetime.datetime = ormar.DateTime(
default=datetime.datetime.now, name="modification_date" default=datetime.datetime.now, name="modification_date"
@ -562,8 +537,7 @@ class UpdateDateFieldsModel(ormar.Model):
class Category(CreateDateFieldsModel, AuditCreateModel): class Category(CreateDateFieldsModel, AuditCreateModel):
class Meta(ormar.ModelMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50, unique=True, index=True) name: str = ormar.String(max_length=50, unique=True, index=True)

View File

@ -20,27 +20,27 @@ For example to list pydantic model fields you can:
## Sqlalchemy Table ## 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: For example to list table columns you can:
```Python hl_lines="20" ```Python hl_lines="24"
--8<-- "../docs_src/models/docs004.py" --8<-- "../docs_src/models/docs004.py"
``` ```
!!!tip !!!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 !!!info
For more options visit official [sqlalchemy-metadata][sqlalchemy-metadata] documentation. For more options visit official [sqlalchemy-metadata][sqlalchemy-metadata] documentation.
## Fields Definition ## 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: For example to list table model fields you can:
```Python hl_lines="20" ```Python hl_lines="22"
--8<-- "../docs_src/models/docs005.py" --8<-- "../docs_src/models/docs005.py"
``` ```

View File

@ -13,19 +13,19 @@ Available methods are described below.
## `pydantic` methods ## `pydantic` methods
Note that each `ormar.Model` is also a `pydantic.BaseModel`, so all `pydantic` methods are also available on a model, 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 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. The benefit of using construct is the speed of execution due to skipped validation.
!!!note !!!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 !!!warning
Bear in mind that due to skipped validation the `construct` method does not perform any conversions, checks etc. Bear in mind that due to skipped validation the `construct` method does not perform any conversions, checks etc.
@ -36,14 +36,14 @@ The benefit of using construct is the speed of execution due to skipped validati
* Providing a `default` value for not set fields * Providing a `default` value for not set fields
* Initialize nested ormar models if you pass a dictionary or a primary key value * 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. 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) ### include (`ormar` modified)
@ -55,7 +55,7 @@ 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. 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` 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. `ormar` does not support by index exclusion/ inclusions and accepts a simplified and more user-friendly notation.
@ -96,10 +96,7 @@ Flag indicates whether fields which were not explicitly set when creating the mo
```python ```python
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="Test") name: str = ormar.String(max_length=100, default="Test")
@ -107,10 +104,7 @@ class Category(ormar.Model):
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -118,17 +112,17 @@ class Item(ormar.Model):
categories: List[Category] = ormar.ManyToMany(Category) categories: List[Category] = ormar.ManyToMany(Category)
category = Category(name="Test 2") 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} '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() await category.save()
category2 = await Category.objects.get() 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} 'visibility': True}
# NOTE how after loading from db all fields are set explicitly # NOTE how after loading from db all fields are set explicitly
# as this is what happens when you populate a model from db # 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} 'name': 'Test 2', 'visibility': True}
``` ```
@ -140,20 +134,14 @@ Flag indicates are equal to their default values (whether set or otherwise) shou
```python ```python
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="Test") name: str = ormar.String(max_length=100, default="Test")
visibility: bool = ormar.Boolean(default=True) visibility: bool = ormar.Boolean(default=True)
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -162,15 +150,15 @@ class Item(ormar.Model):
category = Category() category = Category()
# note that Integer pk is by default autoincrement so optional # note that Integer pk is by default autoincrement so optional
assert category.dict() == {'id': None, 'items': [], 'name': 'Test', 'visibility': True} assert category.model_dump() == {'id': None, 'items': [], 'name': 'Test', 'visibility': True}
assert category.dict(exclude_defaults=True) == {'items': []} assert category.model_dump(exclude_defaults=True) == {'items': []}
# save and reload the data # save and reload the data
await category.save() await category.save()
category2 = await Category.objects.get() category2 = await Category.objects.get()
assert category2.dict() == {'id': 1, 'items': [], 'name': 'Test', 'visibility': True} assert category2.model_dump() == {'id': 1, 'items': [], 'name': 'Test', 'visibility': True}
assert category2.dict(exclude_defaults=True) == {'id': 1, 'items': []} assert category2.model_dump(exclude_defaults=True) == {'id': 1, 'items': []}
``` ```
### exclude_none ### exclude_none
@ -181,10 +169,7 @@ Flag indicates whether fields which are equal to `None` should be excluded from
```python ```python
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="Test", nullable=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 Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -204,16 +186,16 @@ class Item(ormar.Model):
category = Category(name=None) category = Category(name=None)
assert category.dict() == {'id': None, 'items': [], 'name': None, assert category.model_dump() == {'id': None, 'items': [], 'name': None,
'visibility': True} 'visibility': True}
# note the id is not set yet so None and excluded # 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() await category.save()
category2 = await Category.objects.get() category2 = await Category.objects.get()
assert category2.dict() == {'id': 1, 'items': [], 'name': None, assert category2.model_dump() == {'id': 1, 'items': [], 'name': None,
'visibility': True} 'visibility': True}
assert category2.dict(exclude_none=True) == {'id': 1, 'items': [], assert category2.model_dump(exclude_none=True) == {'id': 1, 'items': [],
'visibility': True} 'visibility': True}
``` ```
@ -226,17 +208,14 @@ Setting flag to `True` will exclude all primary key columns in a tree, including
```python ```python
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
item1 = Item(id=1, name="Test Item") item1 = Item(id=1, name="Test Item")
assert item1.dict() == {"id": 1, "name": "Test Item"} assert item1.model_dump() == {"id": 1, "name": "Test Item"}
assert item1.dict(exclude_primary_keys=True) == {"name": "Test Item"} assert item1.model_dump(exclude_primary_keys=True) == {"name": "Test Item"}
``` ```
### exclude_through_models (`ormar` only) ### exclude_through_models (`ormar` only)
@ -249,20 +228,14 @@ Setting the `exclude_through_models=True` will exclude all through models, inclu
```python ```python
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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() item = await Item.objects.select_related("categories").get()
# by default you can see the through models (itemcategory) # 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': [ 'categories': [
{'id': 1, 'name': 'test cat', {'id': 1, 'name': 'test cat',
'itemcategory': {'id': 1, 'category': None, 'item': None}}, 'itemcategory': {'id': 1, 'category': None, 'item': None}},
@ -289,7 +262,7 @@ assert item.dict() == {'id': 1, 'name': 'test',
]} ]}
# you can exclude those fields/ models # 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', 'id': 1, 'name': 'test',
'categories': [ 'categories': [
{'id': 1, 'name': 'test cat'}, {'id': 1, 'name': 'test cat'},
@ -297,19 +270,19 @@ 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. 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)` `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. 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. Moreover, you can pass `exclude` and/or `include` parameters to keep only the fields that you want to, including in nested models.
@ -321,25 +294,21 @@ That means that this way you can effortlessly create pydantic models for request
Given sample ormar models like follows: Given sample ormar models like follows:
```python ```python
metadata = sqlalchemy.MetaData() base_ormar_config = ormar.OrmarConfig(
database = databases.Database(DATABASE_URL, force_rollback=True) metadata=sqlalchemy.MetaData(),
database=databases.Database(DATABASE_URL, force_rollback=True),
)
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
class Category(ormar.Model): class Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Item(ormar.Model): class Item(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="test") name: str = ormar.String(max_length=100, default="test")
@ -382,7 +351,7 @@ class Category(BaseModel):
items: Optional[List[Item]] 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). 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).
@ -392,9 +361,9 @@ Note how `Item` model above does not have a reference to `Category` although in
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. 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. 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. 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). `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' track.album.name # will return 'Malibu'
``` ```
## load_all ## load_all()
`load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None) -> Model` `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, Method works like `load()` but also goes through all relations of the `Model` on which the method is called,
and reloads them from database. 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. 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 !!!warning
All relations are cleared on `load_all()`, so if you exclude some nested models they will be empty after call. All relations are cleared on `load_all()`, so if you exclude some nested models they will be empty after call.
## save ## save()
`save() -> self` `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 await track.save() # will raise integrity error as pk is populated
``` ```
## update ## update()
`update(_columns: List[str] = None, **kwargs) -> self` `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: In example:
```python ```python hl_lines="16"
class Movie(ormar.Model): class Movie(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "movies"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, nullable=False, name="title") name: str = ormar.String(max_length=100, nullable=False, name="title")
@ -512,7 +478,7 @@ assert terminator.year == 1984
!!!warning !!!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` `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. 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 !!!tip
Note that that `track` object stays the same, only record in the database is removed. 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` `save_related(follow: bool = False, save_all: bool = False, exclude=Optional[Union[Set, Dict]]) -> None`
@ -584,18 +550,14 @@ Example:
```python ```python
class Department(ormar.Model): class Department(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
department_name: str = ormar.String(max_length=100) department_name: str = ormar.String(max_length=100)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
course_name: str = ormar.String(max_length=100) course_name: str = ormar.String(max_length=100)
@ -604,9 +566,7 @@ class Course(ormar.Model):
class Student(ormar.Model): class Student(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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 # after excluding ids and through models you get exact same payload used to
# construct whole tree # construct whole tree
assert department_check.dict(exclude=to_exclude) == to_save assert department_check.model_dump(exclude=to_exclude) == to_save
``` ```

View File

@ -16,14 +16,14 @@ engine = sqlalchemy.create_engine("sqlite:///test.db")
metadata.create_all(engine) 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 ```python
import sqlalchemy import sqlalchemy
# get your database url in sqlalchemy format - same as used with databases instance used in Model definition # get your database url in sqlalchemy format - same as used with databases instance used in Model definition
engine = sqlalchemy.create_engine("sqlite:///test.db") engine = sqlalchemy.create_engine("sqlite:///test.db")
# Artist is an ormar model from previous examples # Artist is an ormar model from previous examples
Artist.Meta.table.create(engine) Artist.ormar_config.table.create(engine)
``` ```
!!!warning !!!warning

View File

@ -1,14 +1,6 @@
To provide better errors check you should use mypy with pydantic [plugin][plugin] 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. Please use notation introduced in version 0.4.0.
So you **cannot use the old notation** like this:
```Python hl_lines="15-17"
--8<-- "../docs_src/models/docs011.py"
```
Instead switch to notation introduced in version 0.4.0.
```Python hl_lines="15-17" ```Python hl_lines="15-17"
--8<-- "../docs_src/models/docs012.py" --8<-- "../docs_src/models/docs012.py"

View File

@ -32,10 +32,11 @@ the count will be the total number of rows returned
```python ```python
class Book(ormar.Model): class Book(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "books" database=databases.Database(DATABASE_URL),
metadata = metadata metadata=sqlalchemy.MetaData(),
database = database tablename="book"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) 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 ```python
class Book(ormar.Model): class Book(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "books" database=databases.Database(DATABASE_URL),
metadata = metadata metadata=sqlalchemy.MetaData(),
database = database tablename="book"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)

View File

@ -30,10 +30,12 @@ The allowed kwargs are `Model` fields names and proper value types.
```python ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "album" database=database,
metadata = metadata metadata=metadata,
database = database tablename="album"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -68,10 +70,11 @@ i.e. `get_or_create(_defaults: {"title": "I win"}, title="never used")` will alw
```python ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "album" database=database,
metadata = metadata metadata=metadata,
database = database tablename="album"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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. 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" --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. 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" --8<-- "../docs_src/queries/docs004.py"
``` ```

View File

@ -26,7 +26,7 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai
Return number of rows deleted. Return number of rows deleted.
```python hl_lines="26-30" ```python hl_lines="40-44"
--8<-- "../docs_src/queries/docs005.py" --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 ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "tracks"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) 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 ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "tracks"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)

View File

@ -35,20 +35,14 @@ a filter across an FK relationship.
```python ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "tracks"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)
@ -197,20 +191,14 @@ conditions.
```python ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "tracks"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) 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: Given a sample models like this:
```python ```python
database = databases.Database(DATABASE_URL) base_ormar_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "authors"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "books"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Optional[Author] = ormar.ForeignKey(Author) 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: Given sample Models like following:
```python ```python
--8 < -- "../../docs_src/queries/docs007.py" --8<-- "../docs_src/queries/docs007.py"
``` ```
To order by main model field just provide a field name 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` 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` 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 !!!tip
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation 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 * Explicitly passed `order_by()` calls in query
* Relation passed `orders_by` and `related_orders_by` if exists * Relation passed `orders_by` and `related_orders_by` if exists
* Model `Meta` class `orders_by` * Model's `ormar_config` object `orders_by`
* Model `primary_key` column ascending (fallback, used if none of above provided) * 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).** **Order from only one source is applied to each `Model` (so that you can always overwrite it in a single query).**

View File

@ -46,20 +46,14 @@ To chain related `Models` relation use double underscores between names.
```python ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "tracks"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) 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 ```python
class SchoolClass(ormar.Model): class SchoolClass(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="schoolclasses")
tablename = "schoolclasses"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -93,20 +84,14 @@ class SchoolClass(ormar.Model):
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Student(ormar.Model): class Student(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "students"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -115,10 +100,7 @@ class Student(ormar.Model):
class Teacher(ormar.Model): class Teacher(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "teachers"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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: With sample date like follow:
```python ```python
database = databases.Database(DATABASE_URL, force_rollback=True) base_ormar_config = OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL, force_rollback=True),
metadata=sqlalchemy.MetaData(),
)
class BaseMeta(ormar.ModelMeta):
database = database
metadata = metadata
class Address(ormar.Model): class Address(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="addresses")
tablename = "addresses"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
street: str = ormar.String(max_length=100, nullable=False) street: str = ormar.String(max_length=100, nullable=False)
@ -203,8 +181,7 @@ class Address(ormar.Model):
class Branch(ormar.Model): class Branch(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="branches")
tablename = "branches"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, nullable=False) name: str = ormar.String(max_length=100, nullable=False)
@ -212,8 +189,7 @@ class Branch(ormar.Model):
class Company(ormar.Model): class Company(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="companies")
tablename = "companies"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, nullable=False, name="company_name") 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 ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "tracks"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)
@ -301,10 +271,7 @@ You can provide a string, or a list of strings
```python ```python
class SchoolClass(ormar.Model): class SchoolClass(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="schoolclasses")
tablename = "schoolclasses"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -312,20 +279,14 @@ class SchoolClass(ormar.Model):
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Student(ormar.Model): class Student(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "students"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -334,10 +295,7 @@ class Student(ormar.Model):
class Teacher(ormar.Model): class Teacher(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "teachers"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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 #### 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, That means that in `select_related` example above you will always have 10 000 Models A,
30 000 Models B 30 000 Models B
(even if the unique number of rows in db is 3 - processing of `select_related` spawns ** (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. **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 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 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 ```python
# will return False (note that id is a python `builtin` function not ormar one). # 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 # from above - will also return False
id(model1) == id(model2) id(model1) == id(model2)

View File

@ -22,10 +22,11 @@ Combines the `offset` and `limit` methods based on page number and size
```python ```python
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "track" database=databases.Database(DATABASE_URL),
metadata = metadata metadata=sqlalchemy.MetaData(),
database = database tablename="track"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)
@ -52,10 +53,11 @@ use the `limit_raw_sql` parameter flag, and set it to `True`.
```python ```python
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar.OrmarConfig(
tablename = "track" database=databases.Database(DATABASE_URL),
metadata = metadata metadata=sqlalchemy.MetaData(),
database = database tablename="track"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)
@ -86,10 +88,11 @@ use the `limit_raw_sql` parameter flag, and set it to `True`.
```python ```python
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar.OrmarConfig(
tablename = "track" database=databases.Database(DATABASE_URL),
metadata = metadata metadata=sqlalchemy.MetaData(),
database = database tablename="track"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)

View File

@ -40,8 +40,7 @@ Example:
# declared models # declared models
class Category(ormar.Model): class Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)
@ -49,8 +48,7 @@ class Category(ormar.Model):
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "posts"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=200) 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 # declare models
class User(ormar.Model): class User(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Role(ormar.Model): class Role(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -216,8 +212,7 @@ Example:
# declared models # declared models
class Category(ormar.Model): class Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)
@ -225,8 +220,7 @@ class Category(ormar.Model):
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "posts"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=200) name: str = ormar.String(max_length=200)
@ -257,8 +251,7 @@ Let's complicate the relation and modify the previously mentioned Category model
```python ```python
class Category(ormar.Model): class Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)

View File

@ -31,10 +31,11 @@ Passing a criteria is actually calling filter(*args, **kwargs) method described
```python ```python
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "track" database=database,
metadata = metadata metadata=metadata,
database = database tablename="track"
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)
@ -74,10 +75,7 @@ a new one with given kwargs and _defaults.
```python ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="album")
tablename = "album"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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 ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="album")
tablename = "album"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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 ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="album")
tablename = "album"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="track")
tablename = "track"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) 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 ```python
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="album")
tablename = "album"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -24,52 +24,7 @@ With `fields()` you can select subset of model columns to limit the data load.
Given a sample data like following: Given a sample data like following:
```python ```python
import databases --8<-- "../docs_src/select_columns/docs001.py"
import sqlalchemy
import ormar
from tests.settings import DATABASE_URL
database = databases.Database(DATABASE_URL, force_rollback=True)
metadata = sqlalchemy.MetaData()
class Company(ormar.Model):
class Meta:
tablename = "companies"
metadata = metadata
database = database
id: 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')
``` ```
You can select specified fields by passing a `str, List[str], Set[str] or dict` with 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 To include related models use
notation `{related_name}__{column}[__{optional_next} etc.]`. notation `{related_name}__{column}[__{optional_next} etc.]`.
```python hl_lines="1" ```python hl_lines="1-6"
all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all() all_cars = await (
Car.objects
.select_related('manufacturer')
.fields(['id', 'name', 'manufacturer__name'])
.all()
)
for car in all_cars: for car in all_cars:
# excluded columns will yield None # excluded columns will yield None
assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) 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. - implies a list of all fields for those nested models.
```python hl_lines="1" ```python hl_lines="1-7"
all_cars = await Car.objects.select_related('manufacturer').fields('id').fields( all_cars = await (
['name']).all() Car.objects
.select_related('manufacturer')
.fields('id')
.fields(['name'])
.all()
)
# all fields from company model are selected # all fields from company model are selected
assert all_cars[0].manufacturer.name == 'Toyota' assert all_cars[0].manufacturer.name == 'Toyota'
assert all_cars[0].manufacturer.founded == 1937 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. You cannot exclude mandatory model columns - `manufacturer__name` in this example.
```python ```python
await Car.objects.select_related('manufacturer').fields( await (
['id', 'name', 'manufacturer__founded']).all() Car.objects
.select_related('manufacturer')
.fields(['id', 'name', 'manufacturer__founded'])
.all()
)
# will raise pydantic ValidationError as company.name is required # will raise pydantic ValidationError as company.name is required
``` ```
@ -138,38 +107,71 @@ Below you can see examples that are equivalent:
```python ```python
# 1. like in example above # 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 # 2. to mark a field as required use ellipsis
await Car.objects.select_related('manufacturer').fields({'id': ..., await (
Car.objects
.select_related('manufacturer')
.fields({'id': ...,
'name': ..., 'name': ...,
'manufacturer': { 'manufacturer': {
'name': ...} 'name': ...
}).all() }
})
.all()
)
# 3. to include whole nested model use ellipsis # 3. to include whole nested model use ellipsis
await Car.objects.select_related('manufacturer').fields({'id': ..., await (
Car.objects
.select_related('manufacturer')
.fields({'id': ...,
'name': ..., 'name': ...,
'manufacturer': ... 'manufacturer': ...
}).all() })
.all()
)
# 4. to specify fields at last nesting level you can also use set - equivalent to 2. above # 4. to specify fields at last nesting level
await Car.objects.select_related('manufacturer').fields({'id': ..., # you can also use set - equivalent to 2. above
await (
Car.objects
.select_related('manufacturer')
.fields({'id': ...,
'name': ..., 'name': ...,
'manufacturer': {'name'} 'manufacturer': {'name'}
}).all() })
.all()
)
# 5. of course set can have multiple fields # 5. of course set can have multiple fields
await Car.objects.select_related('manufacturer').fields({'id': ..., await (
Car.objects
.select_related('manufacturer')
.fields({'id': ...,
'name': ..., 'name': ...,
'manufacturer': {'name', 'founded'} 'manufacturer': {'name', 'founded'}
}).all() })
.all()
)
# 6. you can include all nested fields but it will be equivalent of 3. above which is shorter # 6. you can include all nested fields,
await Car.objects.select_related('manufacturer').fields({'id': ..., # but it will be equivalent of 3. above which is shorter
await (
Car.objects
.select_related('manufacturer')
.fields({'id': ...,
'name': ..., 'name': ...,
'manufacturer': {'id', 'name', 'founded'} 'manufacturer': {'id', 'name', 'founded'}
}).all() })
.all()
)
``` ```
@ -201,74 +203,65 @@ exclude fields from whole hierarchy.
Below you can find few simple examples: Below you can find few simple examples:
```python hl_lines="47 48 60 61 67" ```python
import databases --8<-- "../docs_src/select_columns/docs001.py"
import sqlalchemy ```
import ormar ```python
from tests.settings import DATABASE_URL # select manufacturer but only name,
# to include related models use notation {model_name}__{column}
database = databases.Database(DATABASE_URL, force_rollback=True) all_cars = await (
metadata = sqlalchemy.MetaData() Car.objects
.select_related('manufacturer')
.exclude_fields([
class Company(ormar.Model): 'year',
class Meta: 'gearbox_type',
tablename = "companies" 'gears',
metadata = metadata 'aircon_type',
database = database 'company__founded'
])
id: int = ormar.Integer(primary_key=True) .all()
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()
for car in all_cars: for car in all_cars:
# excluded columns will yield None # excluded columns will yield None
assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) assert all(getattr(car, x) is None
# included column on related models will be available, pk column is always included 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 # even if you do not include it in fields list
assert car.manufacturer.name == 'Toyota' 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 assert car.manufacturer.founded is None
# fields() can be called several times, building up the columns to select # fields() can be called several times,
# models selected in select_related but with no columns in fields list implies all fields # building up the columns to select
all_cars = await Car.objects.select_related('manufacturer').exclude_fields('year').exclude_fields( # models included in select_related
['gear', 'gearbox_type']).all() # 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 # all fields from company model are selected
assert all_cars[0].manufacturer.name == 'Toyota' assert all_cars[0].manufacturer.name == 'Toyota'
assert all_cars[0].manufacturer.founded == 1937 assert all_cars[0].manufacturer.founded == 1937
# cannot exclude mandatory model columns - company__name in this example - note usage of dict/set this time # cannot exclude mandatory model columns,
await Car.objects.select_related('manufacturer').exclude_fields([{'company': {'name'}}]).all() # 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 # will raise pydantic ValidationError as company.name is required
``` ```

View File

@ -29,7 +29,7 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai
Return number of rows updated. Return number of rows updated.
```Python hl_lines="26-28" ```Python hl_lines="42-44"
--8<-- "../docs_src/queries/docs002.py" --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. 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" --8<-- "../docs_src/queries/docs003.py"
``` ```

View File

@ -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`. 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" --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: 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" --8<-- "../docs_src/fields/docs001.py"
``` ```
@ -45,15 +45,14 @@ But you cannot:
* Access the related field from reverse model with `related_name` * 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) * 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`. * 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: Example:
```python ```python
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
first_name: str = ormar.String(max_length=80) first_name: str = ormar.String(max_length=80)
@ -61,8 +60,7 @@ class Author(ormar.Model):
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)
@ -82,8 +80,8 @@ authors = (
assert authors[0].first_name == "Test" assert authors[0].first_name == "Test"
# note that posts are not populated for author even if explicitly # note that posts are not populated for author even if explicitly
# included in select_related - note no posts in dict() # included in select_related - note no posts in model_dump()
assert author.dict(exclude={"id"}) == {"first_name": "Test", "last_name": "Author"} assert author.model_dump(exclude={"id"}) == {"first_name": "Test", "last_name": "Author"}
# still can filter through fields of related model # still can filter through fields of related model
authors = await Author.objects.filter(posts__title="Test Post").all() authors = await Author.objects.filter(posts__title="Test Post").all()
@ -112,7 +110,7 @@ assert department.courses[0] == course
!!!warning !!!warning
If you want to add child model on related model the primary key value for parent model **has to exist in database**. 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. 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()`. 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. 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 ```python
# nulls department column on all courses related to this department # 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 ## 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" --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. 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" --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. 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" --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. 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" --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). 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" --8<-- "../docs_src/relations/docs001.py"
``` ```

View File

@ -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. To define many-to-one relation use `ForeignKey` field.
```Python hl_lines="17" ```Python hl_lines="26"
--8<-- "../docs_src/relations/docs003.py" --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. 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 Department(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -52,21 +50,22 @@ class Department(ormar.Model):
To define many-to-many relation use `ManyToMany` field. To define many-to-many relation use `ManyToMany` field.
```python hl_lines="18" ```python hl_lines="19"
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "categories" database=database,
database = database metadata=metadata,
metadata = metadata tablename="categories",
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)
class Post(ormar.Model): class Post(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "posts" database=database,
database = database metadata=metadata,
metadata = metadata )
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) 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. 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. 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 Category(ormar.Model):
class Meta(BaseMeta): ormar_config = ormar.OrmarConfig(
tablename = "categories" database=database,
metadata=metadata,
tablename="categories",
)
id = ormar.Integer(primary_key=True) id = ormar.Integer(primary_key=True)
name = ormar.String(max_length=40) name = ormar.String(max_length=40)
# you can specify additional fields on through model # you can specify additional fields on through model
class PostCategory(ormar.Model): class PostCategory(ormar.Model):
class Meta(BaseMeta): ormar_config = ormar.OrmarConfig(
tablename = "posts_x_categories" database=database,
metadata=metadata,
tablename="posts_x_categories",
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
sort_order: int = ormar.Integer(nullable=True) sort_order: int = ormar.Integer(nullable=True)
@ -111,8 +116,10 @@ class PostCategory(ormar.Model):
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = ormar.OrmarConfig(
pass database=database,
metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)
@ -130,7 +137,7 @@ class Post(ormar.Model):
## Relationship default sort order ## 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 !!!tip
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation 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: Sample configuration might look like this:
```python hl_lines="24" ```python hl_lines="23"
database = databases.Database(DATABASE_URL) database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class BaseMeta(ormar.ModelMeta): base_ormar_config = ormar.OrmarConfig(
metadata = metadata database=database,
database = database metadata=metadata,
)
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "authors"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "books"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Optional[Author] = ormar.ForeignKey( 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 different relations (remember the reverse side is auto-registered for you), you need to use
`ForwardRef` from `typing` module. `ForwardRef` from `typing` module.
```python hl_lines="1 11 14" ```python hl_lines="1 9 12"
PersonRef = ForwardRef("Person") PersonRef = ForwardRef("Person")
class Person(ormar.Model): class Person(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -9,7 +9,7 @@ Sqlalchemy column and Type are automatically taken from target `Model`.
## Defining Models ## Defining Models
```Python hl_lines="40" ```Python hl_lines="34"
--8<-- "../docs_src/relations/docs002.py" --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. `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 Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) 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` If you are sure you don't want the reverse relation you can use `skip_reverse=True`
flag of the `ManyToMany`. flag of the `ManyToMany`.
If you set `skip_reverse` flag internally the field is still registered on the other If you set `skip_reverse` flag internally the field is still registered on the other
side of the relationship so you can: side of the relationship so you can:
* `filter` by related models fields from reverse model * `filter` by related models fields from reverse model
* `order_by` 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` * 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) * 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`. * 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: Example:
```python ```python
class Category(ormar.Model): class Category(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)
@ -126,8 +124,8 @@ categories = (
assert categories[0].first_name == "Test" assert categories[0].first_name == "Test"
# note that posts are not populated for author even if explicitly # note that posts are not populated for author even if explicitly
# included in select_related - note no posts in dict() # included in select_related - note no posts in model_dump()
assert news.dict(exclude={"id"}) == {"name": "News"} assert news.model_dump(exclude={"id"}) == {"name": "News"}
# still can filter through fields of related model # still can filter through fields of related model
categories = await Category.objects.filter(posts__title="Hello, M2M").all() 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 Optionally if you want to add additional fields you can explicitly create and pass
the through model class. the through model class.
```Python hl_lines="14-20 29" ```Python hl_lines="19-24 32"
--8<-- "../docs_src/relations/docs004.py" --8<-- "../docs_src/relations/docs004.py"
``` ```
@ -170,9 +168,7 @@ So in example like this:
```python ```python
... # course declaration omitted ... # course declaration omitted
class Student(ormar.Model): class Student(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -180,10 +176,7 @@ class Student(ormar.Model):
# will produce default Through model like follows (example simplified) # will produce default Through model like follows (example simplified)
class StudentCourse(ormar.Model): class StudentCourse(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="students_courses")
database = database
metadata = metadata
tablename = "students_courses"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
student = ormar.ForeignKey(Student) # default name student = ormar.ForeignKey(Student) # default name
@ -199,10 +192,14 @@ Example:
```python ```python
... # course declaration omitted ... # course declaration omitted
base_ormar_config = ormar.OrmarConfig(
database=databases.Database("sqlite:///db.sqlite"),
metadata=sqlalchemy.MetaData(),
)
class Student(ormar.Model): class Student(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -212,10 +209,7 @@ class Student(ormar.Model):
# will produce Through model like follows (example simplified) # will produce Through model like follows (example simplified)
class StudentCourse(ormar.Model): class StudentCourse(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="student_courses")
database = database
metadata = metadata
tablename = "students_courses"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
student_id = ormar.ForeignKey(Student) # set by through_relation_name 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: 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" --8<-- "../docs_src/relations/docs004.py"
``` ```

View File

@ -14,21 +14,8 @@ First, you need to import the required ref from typing.
from typing import ForwardRef 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, 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 ```python
# create the forwardref to model Person # create the forwardref to model Person
@ -36,9 +23,7 @@ PersonRef = ForwardRef("Person")
class Person(ormar.Model): class Person(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -72,9 +57,7 @@ PersonRef = ForwardRef("Person")
class Person(ormar.Model): class Person(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -93,14 +76,10 @@ and through parameters.
ChildRef = ForwardRef("Child") ChildRef = ForwardRef("Child")
class ChildFriend(ormar.Model): class ChildFriend(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
class Child(ormar.Model): class Child(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -132,9 +111,7 @@ TeacherRef = ForwardRef("Teacher")
class Student(ormar.Model): class Student(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -144,16 +121,11 @@ class Student(ormar.Model):
class StudentTeacher(ormar.Model): class StudentTeacher(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy(tablename='students_x_teachers')
tablename = 'students_x_teachers'
metadata = metadata
database = db
class Teacher(ormar.Model): class Teacher(ormar.Model):
class Meta(ModelMeta): ormar_config = base_ormar_config.copy()
metadata = metadata
database = db
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -58,7 +58,7 @@ assert post.categories[0] == news
`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` `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 !!!tip
Read more in queries documentation [get_or_create][get_or_create] Read more in queries documentation [get_or_create][get_or_create]
@ -127,7 +127,7 @@ provided Through model.
Given sample like this: Given sample like this:
```Python hl_lines="14-20 29" ```Python hl_lines="19-24 32"
--8<-- "../docs_src/relations/docs004.py" --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 Note that for `ManyToMany` relations update can also accept an argument with through field
name and a dictionary of fields. name and a dictionary of fields.
```Python hl_lines="14-20 29" ```Python hl_lines="19-24 32"
--8<-- "../docs_src/relations/docs004.py" --8<-- "../docs_src/relations/docs004.py"
``` ```

File diff suppressed because it is too large Load Diff

View File

@ -14,15 +14,15 @@ import sqlalchemy
import ormar 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 Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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. 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" --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. 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" --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 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. queries inside your receivers if you want to.
```Python hl_lines="15-22" ```Python hl_lines="41-48"
--8<-- "../docs_src/signals/docs002.py" --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 # define a dummy debug function
@pre_update([Album, Track]) @pre_update([Album, Track])
async def before_update(sender, instance, **kwargs): 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 ```python
@pre_update(Album) @pre_update(Album)
async def before_update(sender, instance, **kwargs): 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) @pre_update(Album)
async def before_update2(sender, instance, **kwargs): async def before_update2(sender, instance, **kwargs):
@ -100,13 +100,13 @@ class AlbumAuditor:
async def before_save(self, sender, instance, **kwargs): async def before_save(self, sender, instance, **kwargs):
await AuditLog( 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() ).save()
auditor = AlbumAuditor() auditor = AlbumAuditor()
pre_save(Album)(auditor.before_save) pre_save(Album)(auditor.before_save)
# call above has same result like the one below # 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 # signals are also exposed on instance
album = Album(name='Miami') album = Album(name='Miami')
album.signals.pre_save.connect(auditor.before_save) album.signals.pre_save.connect(auditor.before_save)
@ -127,7 +127,7 @@ async def before_update(sender, instance, **kwargs):
instance.is_best_seller = True instance.is_best_seller = True
# disconnect given function from signal for given Model # 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 # signals are also exposed on instance
album = Album(name='Miami') album = Album(name='Miami')
album.signals.pre_save.disconnect(before_save) 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. * 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 * 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 ### pre_save
@ -251,23 +251,23 @@ import sqlalchemy
import ormar 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 Album(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
tablename = "albums"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False) is_best_seller: bool = ormar.Boolean(default=False)
play_count: int = ormar.Integer(default=0) play_count: int = ormar.Integer(default=0)
Album.Meta.signals.your_custom_signal = ormar.Signal() Album.ormar_config.signals.your_custom_signal = ormar.Signal()
Album.Meta.signals.your_custom_signal.connect(your_receiver_name) 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 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. 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. Now to trigger this signal you need to call send method of the Signal.
```python ```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. 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. Additional parameters have to be passed as keyword arguments.
```python ```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)
``` ```

View File

@ -15,10 +15,10 @@ async with database.transaction():
``` ```
!!!note !!!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`. 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. be reached from both class and instance.
```python ```python
@ -26,24 +26,25 @@ import databases
import sqlalchemy import sqlalchemy
import ormar 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 Author(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy()
database=database
metadata=metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=255) name: str = ormar.String(max_length=255)
# database is accessible from class # database is accessible from class
database = Author.Meta.database database = Author.ormar_config.database
# as well as from instance # as well as from instance
author = Author(name="Stephen King") 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: You can also use `.transaction()` as a function decorator on any async function:

View File

@ -1,33 +1,32 @@
from typing import Optional from typing import Optional
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
from tests.settings import DATABASE_URL from tests.settings import DATABASE_URL
database = databases.Database(DATABASE_URL) database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class BaseMeta(ormar.ModelMeta): base_ormar_config = ormar.OrmarConfig(
metadata = metadata metadata=metadata,
database = database database=database,
)
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy(tablename="authors", order_by=["-name"])
tablename = "authors"
order_by = ["-name"]
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta):
tablename = "books" ormar_config = base_ormar_config.copy(
order_by = ["year", "-ranking"] tablename="books", order_by=["year", "-ranking"]
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Optional[Author] = ormar.ForeignKey(Author) author: Optional[Author] = ormar.ForeignKey(Author)

View File

@ -1,46 +1,23 @@
from typing import List, Optional from typing import List, Optional
import databases
import sqlalchemy
from fastapi import FastAPI
import ormar import ormar
from fastapi import FastAPI
from tests.lifespan import lifespan
from tests.settings import create_config
app = FastAPI() base_ormar_config = create_config()
metadata = sqlalchemy.MetaData() app = FastAPI(lifespan=lifespan(base_ormar_config))
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()
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = base_ormar_config.copy(tablename="items")
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -68,7 +45,7 @@ async def create_category(category: Category):
@app.put("/items/{item_id}") @app.put("/items/{item_id}")
async def get_item(item_id: int, item: Item): async def get_item(item_id: int, item: Item):
item_db = await Item.objects.get(pk=item_id) 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}") @app.delete("/items/{item_id}")

View File

@ -1,16 +1,16 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id = ormar.Integer(primary_key=True) id = ormar.Integer(primary_key=True)
name = ormar.String(max_length=100) name = ormar.String(max_length=100)

View File

@ -1,27 +1,27 @@
import asyncio
from typing import Optional from typing import Optional
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
from examples import create_drop_database
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)
class Department(ormar.Model): class Department(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="departments")
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -29,12 +29,17 @@ class Course(ormar.Model):
department: Optional[Department] = ormar.ForeignKey(Department) department: Optional[Department] = ormar.ForeignKey(Department)
department = await Department(name="Science").save() @create_drop_database(base_config=ormar_base_config)
course = Course(name="Math", completed=False, department=department) 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: asyncio.run(verify())
# Course(id=None,
# name='Math',
# completed=False,
# department=Department(id=None, name='Science'))

View File

@ -1,32 +1,32 @@
from typing import Optional from typing import Optional
import databases import databases
import ormar
import sqlalchemy import sqlalchemy
import ormar DATABASE_URL = "sqlite:///test.db"
database = databases.Database("sqlite:///db.sqlite") ormar_base_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)
class Department(ormar.Model): class Department(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) 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") department = Department(name="Science")

View File

@ -1,27 +1,28 @@
from typing import Optional from typing import Optional
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Department(ormar.Model): class Department(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,20 +1,19 @@
from datetime import datetime from datetime import datetime
import databases import databases
import ormar
import sqlalchemy import sqlalchemy
from sqlalchemy import func, text from sqlalchemy import func, text
import ormar
database = databases.Database("sqlite:///test.db") database = databases.Database("sqlite:///test.db")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Product(ormar.Model): class Product(ormar.Model):
class Meta:
tablename = "product" ormar_config = ormar.OrmarConfig(
metadata = metadata database=database, metadata=metadata, tablename="product"
database = database )
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,16 +1,16 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,19 +1,20 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta:
ormar_config = ormar.OrmarConfig(
database=database,
metadata=metadata,
# if you omit this parameter it will be created automatically # if you omit this parameter it will be created automatically
# as class.__name__.lower()+'s' -> "courses" in this example # as class.__name__.lower()+'s' -> "courses" in this example
tablename = "my_courses" tablename="my_courses",
database = database )
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,34 +1,34 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) completed: bool = ormar.Boolean(default=False)
print(Course.__fields__) print(Course.model_fields)
""" """
Will produce: Will produce:
{'id': ModelField(name='id', {'id': Field(name='id',
type=Optional[int], type=Optional[int],
required=False, required=False,
default=None), default=None),
'name': ModelField(name='name', 'name': Field(name='name',
type=Optional[str], type=Optional[str],
required=False, required=False,
default=None), default=None),
'completed': ModelField(name='completed', 'completed': Field(name='completed',
type=bool, type=bool,
required=False, required=False,
default=False)} default=False)}

View File

@ -1,24 +1,28 @@
import databases import databases
import ormar
import sqlalchemy import sqlalchemy
import ormar DATABASE_URL = "sqlite:///test.db"
database = databases.Database("sqlite:///db.sqlite") ormar_base_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)
class Course(ormar.Model): class Course(ormar.Model):
class Meta(ormar.ModelMeta): # note you don't have to subclass - but it's recommended for ide completion and mypy ormar_config = ormar.OrmarConfig(
database = database tablename="courses",
metadata = metadata database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) completed: bool = ormar.Boolean(default=False)
print(Course.Meta.table.columns) print(Course.ormar_config.table.columns)
""" """
Will produce: Will produce:
['courses.id', 'courses.name', 'courses.completed'] ImmutableColumnCollection(courses.id, courses.name, courses.completed)
""" """

View File

@ -1,67 +1,144 @@
import databases import pprint
import sqlalchemy
import databases
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta(ormar.ModelMeta): ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) 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: Will produce:
{'completed': mappingproxy({'autoincrement': False, {'id': {'__type__': <class 'int'>,
'choices': set(), '__pydantic_type__': <class 'int'>,
'column_type': Boolean(), '__sample__': 0,
'default': False, 'related_name': None,
'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(), 'column_type': Integer(),
'default': None, 'constraints': [],
'ge': None,
'index': False,
'le': None,
'maximum': None,
'minimum': None,
'multiple_of': None,
'name': 'id', 'name': 'id',
'nullable': False, 'db_alias': None,
'primary_key': True, 'primary_key': True,
'pydantic_only': False, 'autoincrement': True,
'server_default': None, 'nullable': True,
'unique': False}), 'sql_nullable': False,
'name': mappingproxy({'allow_blank': False,
'autoincrement': False,
'choices': set(),
'column_type': String(max_length=100),
'curtail_length': None,
'default': None,
'index': 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, 'max_length': 100,
'min_length': None, 'min_length': None,
'name': 'name', 'regex': None},
'nullable': False, '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, 'primary_key': False,
'pydantic_only': False, 'autoincrement': False,
'regex': None, '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, 'server_default': None,
'strip_whitespace': False, 'comment': None,
'unique': False})} 'represent_as_base64_str': False}}
""" """

View File

@ -1,20 +1,20 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
# define your constraints in Meta class of the model # define your constraints in OrmarConfig of the model
# it's a list that can contain multiple constraints # it's a list that can contain multiple constraints
# hera a combination of name and column will have to be unique in db # hera a combination of name and column will have to be unique in db
constraints = [ormar.UniqueColumns("name", "completed")] constraints=[ormar.UniqueColumns("name", "completed")],
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,23 +1,31 @@
import asyncio
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
from examples import create_drop_database
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="courses")
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) completed: bool = ormar.Boolean(default=False)
course = Course(name="Painting for dummies", completed=False) @create_drop_database(base_config=ormar_base_config)
await course.save() 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())

View File

@ -1,17 +1,19 @@
import databases import databases
import ormar
import sqlalchemy 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() metadata = sqlalchemy.MetaData()
class Child(ormar.Model): class Child(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "children" database=database,
metadata = metadata metadata=metadata,
database = database tablename="children",
)
id: int = ormar.Integer(name="child_id", primary_key=True) id: int = ormar.Integer(name="child_id", primary_key=True)
first_name: str = ormar.String(name="fname", max_length=100) first_name: str = ormar.String(name="fname", max_length=100)

View File

@ -1,20 +1,33 @@
from typing import Optional from typing import Optional
import databases import databases
import sqlalchemy
import ormar import ormar
from .docs010 import Artist # previous example import sqlalchemy
database = databases.Database("sqlite:///test.db", force_rollback=True) database = databases.Database("sqlite:///test.db", force_rollback=True)
metadata = sqlalchemy.MetaData() 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 Album(ormar.Model):
class Meta:
tablename = "music_albums" ormar_config = ormar.OrmarConfig(
metadata = metadata database=database,
database = database metadata=metadata,
tablename="music_albums",
)
id: int = ormar.Integer(name="album_id", primary_key=True) id: int = ormar.Integer(name="album_id", primary_key=True)
name: str = ormar.String(name="album_name", max_length=100) name: str = ormar.String(name="album_name", max_length=100)

View File

@ -1,25 +1,40 @@
import databases import databases
import ormar
import sqlalchemy import sqlalchemy
import ormar DATABASE_URl = "sqlite:///test.db"
from .docs008 import Child
database = databases.Database("sqlite:///test.db", force_rollback=True) database = databases.Database(DATABASE_URl, force_rollback=True)
metadata = sqlalchemy.MetaData() 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 ArtistChildren(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "children_x_artists" database=database,
metadata = metadata metadata=metadata,
database = database tablename="children_x_artists",
)
class Artist(ormar.Model): class Artist(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
tablename = "artists" database=database,
metadata = metadata metadata=metadata,
database = database tablename="artists",
)
id: int = ormar.Integer(name="artist_id", primary_key=True) id: int = ormar.Integer(name="artist_id", primary_key=True)
first_name: str = ormar.String(name="fname", max_length=100) first_name: str = ormar.String(name="fname", max_length=100)

View File

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

View File

@ -1,16 +1,16 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id = ormar.Integer(primary_key=True) id = ormar.Integer(primary_key=True)
name = ormar.String(max_length=100) name = ormar.String(max_length=100)

View File

@ -1,27 +1,22 @@
from typing import Optional from typing import Optional
import databases import databases
import ormar
import sqlalchemy import sqlalchemy
import ormar DATABASE_URL = "sqlite:///test.db"
database = databases.Database("sqlite:///test.db", force_rollback=True) ormar_base_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
# note that you do not have to subclass ModelMeta,
# it's useful for type hints and code completion
class MainMeta(ormar.ModelMeta):
metadata = metadata
database = database
class Artist(ormar.Model): class Artist(ormar.Model):
class Meta(MainMeta):
# note that tablename is optional # note that tablename is optional
# if not provided ormar will user class.__name__.lower()+'s' # if not provided ormar will user class.__name__.lower()+'s'
# -> artists in this example # -> artists in this example
pass ormar_config = ormar_base_config.copy()
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
first_name: str = ormar.String(max_length=100) first_name: str = ormar.String(max_length=100)
@ -30,8 +25,7 @@ class Artist(ormar.Model):
class Album(ormar.Model): class Album(ormar.Model):
class Meta(MainMeta): ormar_config = ormar_base_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,18 +1,19 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import pydantic
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) 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)

View File

@ -1,22 +1,21 @@
import databases import databases
import sqlalchemy
import ormar import ormar
from ormar import property_field import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean(default=False) completed: bool = ormar.Boolean(default=False)
@property_field @property
def prefixed_name(self): def prefixed_name(self):
return "custom_prefix__" + self.name return "custom_prefix__" + self.name

View File

@ -1,19 +1,19 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import pydantic
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
class Config: model_config = pydantic.ConfigDict(frozen=True)
allow_mutation = False
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,20 +1,20 @@
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
# define your constraints in Meta class of the model # define your constraints in OrmarConfig of the model
# it's a list that can contain multiple constraints # it's a list that can contain multiple constraints
# hera a combination of name and column will have a compound index in the db # 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) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,23 +1,24 @@
import datetime import datetime
import databases
import sqlalchemy
import databases
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
# define your constraints in Meta class of the model # define your constraints in OrmarConfig of the model
# it's a list that can contain multiple constraints # it's a list that can contain multiple constraints
# hera a combination of name and column will have a level check in the db # 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"), ormar.CheckColumns("start_time < end_time", name="date_check"),
] ],
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -4,25 +4,23 @@ import databases
import ormar import ormar
import sqlalchemy import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Album(ormar.Model): class Album(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="album")
tablename = "album"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Track(ormar.Model): class Track(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="track")
tablename = "track"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album) album: Optional[Album] = ormar.ForeignKey(Album)

View File

@ -1,28 +1,47 @@
import asyncio
import databases import databases
import ormar import ormar
import sqlalchemy import sqlalchemy
from examples import create_drop_database
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Book(ormar.Model): class Book(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(
tablename = "books" tablename="books",
metadata = metadata )
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)
author: str = ormar.String(max_length=100) author: str = ormar.String(max_length=100)
genre: str = ormar.String(max_length=100, default='Fiction', genre: str = ormar.String(
choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) max_length=100,
default="Fiction",
)
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') @create_drop_database(base_config=ormar_base_config)
await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') async def run_query():
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='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"
)
await Book.objects.update(each=True, genre='Fiction') await Book.objects.update(each=True, genre="Fiction")
all_books = await Book.objects.filter(genre='Fiction').all() all_books = await Book.objects.filter(genre="Fiction").all()
assert len(all_books) == 3 assert len(all_books) == 3
asyncio.run(run_query())

View File

@ -1,32 +1,51 @@
import asyncio
import databases import databases
import ormar import ormar
import sqlalchemy import sqlalchemy
from examples import create_drop_database
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Book(ormar.Model): class Book(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy()
tablename = "books"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)
author: str = ormar.String(max_length=100) author: str = ormar.String(max_length=100)
genre: str = ormar.String(max_length=100, default='Fiction', genre: str = ormar.String(
choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) max_length=100,
default="Fiction",
)
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') @create_drop_database(base_config=ormar_base_config)
await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') async def run_query():
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='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"
)
# if not exist the instance will be persisted in db # if not exist the instance will be persisted in db
vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction') vol2 = await Book.objects.update_or_create(
assert await Book.objects.count() == 1 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 # 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.update_or_create(id=vol2.id, genre="Historic")
assert await Book.objects.count() == 1 assert await Book.objects.count() == 4
asyncio.run(run_query())

View File

@ -1,30 +1,39 @@
import asyncio
import databases import databases
import ormar import ormar
import sqlalchemy import sqlalchemy
from examples import create_drop_database
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class ToDo(ormar.Model): class ToDo(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="todos")
tablename = "todos"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
text: str = ormar.String(max_length=500) text: str = ormar.String(max_length=500)
completed = ormar.Boolean(default=False) completed = ormar.Boolean(default=False)
# create multiple instances at once with bulk_create @create_drop_database(base_config=ormar_base_config)
await ToDo.objects.bulk_create( async def run_query():
# create multiple instances at once with bulk_create
await ToDo.objects.bulk_create(
[ [
ToDo(text="Buy the groceries."), ToDo(text="Buy the groceries."),
ToDo(text="Call Mum.", completed=True), ToDo(text="Call Mum.", completed=True),
ToDo(text="Send invoices.", completed=True), ToDo(text="Send invoices.", completed=True),
] ]
) )
todoes = await ToDo.objects.all() todoes = await ToDo.objects.all()
assert len(todoes) == 3 assert len(todoes) == 3
asyncio.run(run_query())

View File

@ -1,30 +1,47 @@
import asyncio
import databases import databases
import ormar import ormar
import sqlalchemy import sqlalchemy
from examples import create_drop_database
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Book(ormar.Model): class Book(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="books")
tablename = "books"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)
author: str = ormar.String(max_length=100) author: str = ormar.String(max_length=100)
genre: str = ormar.String(max_length=100, default='Fiction', genre: str = ormar.String(
choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) max_length=100,
default="Fiction",
)
await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') @create_drop_database(base_config=ormar_base_config)
await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') async def run_query():
await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='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"
)
# delete accepts kwargs that will be used in filter # delete accepts kwargs that will be used in filter
# acting in same way as queryset.filter(**kwargs).delete() # acting in same way as queryset.filter(**kwargs).delete()
await Book.objects.delete(genre='Fantasy') # delete all fantasy books await Book.objects.delete(genre="Fantasy") # delete all fantasy books
all_books = await Book.objects.all() all_books = await Book.objects.all()
assert len(all_books) == 2 assert len(all_books) == 2
asyncio.run(run_query())

View File

@ -1,18 +1,20 @@
import asyncio
import databases import databases
import sqlalchemy
import ormar import ormar
from tests.settings import DATABASE_URL import sqlalchemy
from examples import create_drop_database
database = databases.Database(DATABASE_URL, force_rollback=True) DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Company(ormar.Model): class Company(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="companies")
tablename = "companies"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -20,10 +22,7 @@ class Company(ormar.Model):
class Car(ormar.Model): class Car(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="cars")
tablename = "cars"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
manufacturer = ormar.ForeignKey(Company) manufacturer = ormar.ForeignKey(Company)
@ -34,12 +33,34 @@ class Car(ormar.Model):
aircon_type: str = ormar.String(max_length=20, nullable=True) aircon_type: str = ormar.String(max_length=20, nullable=True)
# build some sample data @create_drop_database(base_config=ormar_base_config)
toyota = await Company.objects.create(name="Toyota", founded=1937) async def run_query():
await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, # build some sample data
aircon_type='Manual') toyota = await Company.objects.create(name="Toyota", founded=1937)
await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, await Car.objects.create(
aircon_type='Manual') manufacturer=toyota,
await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, name="Corolla",
aircon_type='Auto') 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())

View File

@ -1,42 +1,46 @@
import asyncio
import databases import databases
import sqlalchemy
import ormar import ormar
from tests.settings import DATABASE_URL import sqlalchemy
from examples import create_drop_database
database = databases.Database(DATABASE_URL, force_rollback=True) DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Owner(ormar.Model): class Owner(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="owners")
tablename = "owners"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Toy(ormar.Model): class Toy(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="toys")
tablename = "toys"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
owner: Owner = ormar.ForeignKey(Owner) owner: Owner = ormar.ForeignKey(Owner)
# build some sample data @create_drop_database(base_config=ormar_base_config)
aphrodite = await Owner.objects.create(name="Aphrodite") async def run_query():
hermes = await Owner.objects.create(name="Hermes") # build some sample data
zeus = await Owner.objects.create(name="Zeus") 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 4", owner=zeus)
await Toy.objects.create(name="Toy 5", owner=hermes) await Toy.objects.create(name="Toy 5", owner=hermes)
await Toy.objects.create(name="Toy 2", owner=aphrodite) await Toy.objects.create(name="Toy 2", owner=aphrodite)
await Toy.objects.create(name="Toy 1", owner=zeus) await Toy.objects.create(name="Toy 1", owner=zeus)
await Toy.objects.create(name="Toy 3", owner=aphrodite) await Toy.objects.create(name="Toy 3", owner=aphrodite)
await Toy.objects.create(name="Toy 6", owner=hermes) await Toy.objects.create(name="Toy 6", owner=hermes)
asyncio.run(run_query())

View File

@ -1,18 +1,21 @@
import asyncio
import databases import databases
import sqlalchemy
import ormar 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) DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL),
metadata=sqlalchemy.MetaData(),
)
class Company(ormar.Model): class Company(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="companies")
tablename = "companies"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -20,10 +23,7 @@ class Company(ormar.Model):
class Car(ormar.Model): class Car(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="cars")
tablename = "cars"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
manufacturer = ormar.ForeignKey(Company) manufacturer = ormar.ForeignKey(Company)
@ -34,35 +34,81 @@ class Car(ormar.Model):
aircon_type: str = ormar.String(max_length=20, nullable=True) aircon_type: str = ormar.String(max_length=20, nullable=True)
# build some sample data @create_drop_database(base_config=ormar_base_config)
toyota = await Company.objects.create(name="Toyota", founded=1937) async def run_query():
await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, # build some sample data
aircon_type='Manual') toyota = await Company.objects.create(name="Toyota", founded=1937)
await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, await Car.objects.create(
aircon_type='Manual') manufacturer=toyota,
await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, name="Corolla",
aircon_type='Auto') 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} # select manufacturer but only name,
all_cars = await Car.objects.select_related('manufacturer').exclude_fields( # to include related models use notation {model_name}__{column}
['year', 'gearbox_type', 'gears', 'aircon_type', 'company__founded']).all() all_cars = (
for car in 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 # excluded columns will yield None
assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) assert all(
# included column on related models will be available, pk column is always included 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 # even if you do not include it in fields list
assert car.manufacturer.name == 'Toyota' 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 assert car.manufacturer.founded is None
# fields() can be called several times, building up the columns to select # fields() can be called several times,
# models selected in select_related but with no columns in fields list implies all fields # building up the columns to select,
all_cars = await Car.objects.select_related('manufacturer').exclude_fields('year').exclude_fields( # models selected in select_related
['gear', 'gearbox_type']).all() # but with no columns in fields list implies all fields
# all fields from company model are selected all_cars = (
assert all_cars[0].manufacturer.name == 'Toyota' await Car.objects.select_related("manufacturer")
assert all_cars[0].manufacturer.founded == 1937 .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 # cannot exclude mandatory model columns -
await Car.objects.select_related('manufacturer').exclude_fields([{'company': {'name'}}]).all() # manufacturer__name in this example - note usage of dict/set this time
# will raise pydantic ValidationError as company.name is required 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())

View File

@ -1,33 +1,71 @@
# 1. like in example above import asyncio
await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all()
# 2. to mark a field as required use ellipsis import databases
await Car.objects.select_related('manufacturer').fields({'id': ..., import ormar
'name': ..., import sqlalchemy
'manufacturer': { from examples import create_drop_database
'name': ...}
}).all()
# 3. to include whole nested model use ellipsis DATABASE_URL = "sqlite:///test.db"
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 ormar_base_config = ormar.OrmarConfig(
await Car.objects.select_related('manufacturer').fields({'id': ..., database=databases.Database(DATABASE_URL),
'name': ..., metadata=sqlalchemy.MetaData(),
'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 class Company(ormar.Model):
await Car.objects.select_related('manufacturer').fields({'id': ..., ormar_config = ormar_base_config.copy(tablename="companies")
'name': ...,
'manufacturer': {'id', 'name', 'founded'} id: int = ormar.Integer(primary_key=True)
}).all() 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())

View File

@ -1,27 +1,28 @@
from typing import Optional, Dict, Union from typing import Dict, Optional, Union
import databases import databases
import sqlalchemy
import ormar import ormar
import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData() metadata = sqlalchemy.MetaData()
class Department(ormar.Model): class Department(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar.OrmarConfig(
database = database database=database,
metadata = metadata metadata=metadata,
)
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) 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) course2 = Course(name="Math II", completed=False, department=department.pk)
# set up a relation with dictionary corresponding to related model # set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.dict()) course3 = Course(name="Math III", completed=False, department=department.model_dump())
# explicitly set up None # explicitly set up None
course4 = Course(name="Math III", completed=False, department=None) course4 = Course(name="Math III", completed=False, department=None)

View File

@ -1,18 +1,18 @@
from typing import Optional, Union, List from typing import List, Optional
import databases import databases
import ormar import ormar
import sqlalchemy import sqlalchemy
database = databases.Database("sqlite:///db.sqlite") DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
ormar_base_config = ormar.OrmarConfig(
database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)
class Author(ormar.Model): class Author(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="authors")
tablename = "authors"
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
first_name: str = ormar.String(max_length=80) first_name: str = ormar.String(max_length=80)
@ -20,20 +20,14 @@ class Author(ormar.Model):
class Category(ormar.Model): class Category(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="categories")
tablename = "categories"
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=40) name: str = ormar.String(max_length=40)
class Post(ormar.Model): class Post(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="posts")
tablename = "posts"
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)

View File

@ -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 Department(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Course(ormar.Model): class Course(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy()
database = database
metadata = metadata
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)

View File

@ -1,19 +1,23 @@
class BaseMeta(ormar.ModelMeta): import databases
database = database import ormar
metadata = metadata 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 Category(ormar.Model):
class Meta(BaseMeta): ormar_config = ormar_base_config.copy(tablename="categories")
tablename = "categories"
id = ormar.Integer(primary_key=True) id = ormar.Integer(primary_key=True)
name = ormar.String(max_length=40) name = ormar.String(max_length=40)
class PostCategory(ormar.Model): class PostCategory(ormar.Model):
class Meta(BaseMeta): ormar_config = ormar_base_config.copy(tablename="posts_x_categories")
tablename = "posts_x_categories"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
sort_order: int = ormar.Integer(nullable=True) sort_order: int = ormar.Integer(nullable=True)
@ -21,8 +25,7 @@ class PostCategory(ormar.Model):
class Post(ormar.Model): class Post(ormar.Model):
class Meta(BaseMeta): ormar_config = ormar_base_config.copy()
pass
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
title: str = ormar.String(max_length=200) title: str = ormar.String(max_length=200)

View File

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

View File

@ -1,5 +1,29 @@
import asyncio
import databases
import ormar
import sqlalchemy
from examples import create_drop_database
from ormar import pre_update 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) @pre_update(Album)
async def before_update(sender, instance, **kwargs): async def before_update(sender, instance, **kwargs):
@ -7,16 +31,21 @@ async def before_update(sender, instance, **kwargs):
instance.is_best_seller = True instance.is_best_seller = True
# here album.play_count ans is_best_seller get default values @create_drop_database(base_config=ormar_base_config)
album = await Album.objects.create(name="Venice") async def run_query():
assert not album.is_best_seller # here album.play_count ans is_best_seller get default values
assert album.play_count == 0 album = await Album.objects.create(name="Venice")
assert not album.is_best_seller
assert album.play_count == 0
album.play_count = 30 album.play_count = 30
# here a trigger is called but play_count is too low # here a trigger is called but play_count is too low
await album.update() await album.update()
assert not album.is_best_seller assert not album.is_best_seller
album.play_count = 60 album.play_count = 60
await album.update() await album.update()
assert album.is_best_seller assert album.is_best_seller
asyncio.run(run_query())

20
docs_src/test_all_docs.py Normal file
View 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
View File

@ -0,0 +1,3 @@
from .utils import create_drop_database
__all__ = ["create_drop_database"]

View File

@ -1,47 +1,45 @@
from contextlib import asynccontextmanager
from typing import List, Optional from typing import List, Optional
import databases import databases
import ormar
import sqlalchemy import sqlalchemy
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
import ormar DATABASE_URL = "sqlite:///test.db"
app = FastAPI() ormar_base_config = ormar.OrmarConfig(
metadata = sqlalchemy.MetaData() database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
database = databases.Database("sqlite:///test.db") )
app.state.database = database
@app.on_event("startup") @asynccontextmanager
async def startup() -> None: async def lifespan(app: FastAPI):
database_ = app.state.database database_ = app.state.database
if not database_.is_connected: if not database_.is_connected:
await database_.connect() await database_.connect()
yield
@app.on_event("shutdown")
async def shutdown() -> None:
database_ = app.state.database database_ = app.state.database
if database_.is_connected: if database_.is_connected:
await database_.disconnect() 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 Category(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="categories")
tablename = "categories"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Item(ormar.Model): class Item(ormar.Model):
class Meta: ormar_config = ormar_base_config.copy(tablename="items")
tablename = "items"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
@ -69,7 +67,7 @@ async def create_category(category: Category):
@app.put("/items/{item_id}") @app.put("/items/{item_id}")
async def get_item(item_id: int, item: Item): async def get_item(item_id: int, item: Item):
item_db = await Item.objects.get(pk=item_id) 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}") @app.delete("/items/{item_id}")

View File

@ -1,45 +1,40 @@
from typing import Optional from typing import Optional
import databases import databases
import pydantic
import ormar import ormar
import pydantic
import sqlalchemy import sqlalchemy
DATABASE_URL = "sqlite:///db.sqlite" 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 # note that this step is optional -> all ormar cares is an individual
# class with name Meta and proper parameters, but this way you do not # OrmarConfig for each of the models, but this way you do not
# have to repeat the same parameters if you use only one database # have to repeat the same parameters if you use only one database
class BaseMeta(ormar.ModelMeta): base_ormar_config = ormar.OrmarConfig(
metadata = metadata database=databases.Database(DATABASE_URL),
database = database metadata=sqlalchemy.MetaData(),
engine=sqlalchemy.create_engine(DATABASE_URL),
)
# Note that all type hints are optional # Note that all type hints are optional
# below is a perfectly valid model declaration # below is a perfectly valid model declaration
# class Author(ormar.Model): # class Author(ormar.Model):
# class Meta(BaseMeta): # ormar_config = base_ormar_config.copy(tablename="authors")
# tablename = "authors"
# #
# id = ormar.Integer(primary_key=True) # <= notice no field types # id = ormar.Integer(primary_key=True) # <= notice no field types
# name = ormar.String(max_length=100) # name = ormar.String(max_length=100)
class Author(ormar.Model): class Author(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "authors"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100) name: str = ormar.String(max_length=100)
class Book(ormar.Model): class Book(ormar.Model):
class Meta(BaseMeta): ormar_config = base_ormar_config.copy()
tablename = "books"
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
author: Optional[Author] = ormar.ForeignKey(Author) author: Optional[Author] = ormar.ForeignKey(Author)
@ -50,10 +45,9 @@ class Book(ormar.Model):
# create the database # create the database
# note that in production you should use migrations # note that in production you should use migrations
# note that this is not required if you connect to existing database # 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 # just to be sure we clear the db before
metadata.drop_all(engine) base_ormar_config.metadata.drop_all(base_ormar_config.engine)
metadata.create_all(engine) base_ormar_config.metadata.create_all(base_ormar_config.engine)
# all functions below are divided into functionality categories # all functions below are divided into functionality categories
@ -381,7 +375,7 @@ async def raw_data():
async def with_connect(function): async def with_connect(function):
# note that for any other backend than sqlite you actually need to # note that for any other backend than sqlite you actually need to
# connect to the database to perform db operations # connect to the database to perform db operations
async with database: async with base_ormar_config.database:
await function() await function()
# note that if you use framework like `fastapi` you shouldn't connect # note that if you use framework like `fastapi` you shouldn't connect
@ -411,4 +405,4 @@ for func in [
asyncio.run(with_connect(func)) asyncio.run(with_connect(func))
# drop the database tables # drop the database tables
metadata.drop_all(engine) base_ormar_config.metadata.drop_all(base_ormar_config.engine)

21
examples/utils.py Normal file
View 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

View File

@ -16,10 +16,10 @@ nav:
- Pydantic only fields: fields/pydantic-fields.md - Pydantic only fields: fields/pydantic-fields.md
- Fields encryption: fields/encryption.md - Fields encryption: fields/encryption.md
- Relations: - Relations:
- relations/index.md - Relation types: relations/index.md
- relations/postponed-annotations.md
- relations/foreign-key.md - relations/foreign-key.md
- relations/many-to-many.md - relations/many-to-many.md
- relations/postponed-annotations.md
- relations/queryset-proxy.md - relations/queryset-proxy.md
- Queries: - Queries:
- queries/index.md - queries/index.md
@ -40,6 +40,7 @@ nav:
- Using ormar in responses: fastapi/response.md - Using ormar in responses: fastapi/response.md
- Using ormar in requests: fastapi/requests.md - Using ormar in requests: fastapi/requests.md
- Use with mypy: mypy.md - Use with mypy: mypy.md
- Migration to v 0.20: migration.md
- PyCharm plugin: plugin.md - PyCharm plugin: plugin.md
- Contributing: contributing.md - Contributing: contributing.md
- Release Notes: releases.md - Release Notes: releases.md

View File

@ -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 :) And what's a better name for python ORM than snakes cabinet :)
""" """
try:
from importlib.metadata import version # type: ignore from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I001
except ImportError: # pragma: no cover from importlib.metadata import version
from importlib_metadata import version # type: ignore
from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I100
from ormar.decorators import ( # noqa: I100 from ormar.decorators import ( # noqa: I100
post_bulk_update, post_bulk_update,
post_delete, post_delete,
@ -36,7 +35,6 @@ from ormar.decorators import ( # noqa: I100
pre_relation_remove, pre_relation_remove,
pre_save, pre_save,
pre_update, pre_update,
property_field,
) )
from ormar.exceptions import ( # noqa: I100 from ormar.exceptions import ( # noqa: I100
ModelDefinitionError, ModelDefinitionError,
@ -44,37 +42,38 @@ from ormar.exceptions import ( # noqa: I100
NoMatch, NoMatch,
) )
from ormar.fields import ( from ormar.fields import (
DECODERS_MAP,
ENCODERS_MAP,
JSON,
SQL_ENCODERS_MAP,
UUID,
BaseField, BaseField,
BigInteger, BigInteger,
Boolean, Boolean,
DECODERS_MAP, CheckColumns,
Date, Date,
DateTime, DateTime,
Decimal, Decimal,
ENCODERS_MAP,
EncryptBackends, EncryptBackends,
Enum, Enum,
Float, Float,
ForeignKey, ForeignKey,
ForeignKeyField, ForeignKeyField,
IndexColumns,
Integer, Integer,
JSON,
LargeBinary, LargeBinary,
ManyToMany, ManyToMany,
ManyToManyField, ManyToManyField,
SQL_ENCODERS_MAP, ReferentialAction,
SmallInteger, SmallInteger,
String, String,
Text, Text,
Time, Time,
UUID,
UniqueColumns, UniqueColumns,
IndexColumns, )
CheckColumns,
ReferentialAction, # noqa: I100
) # noqa: I100 from ormar.models import ExcludableItems, Extra, Model, OrmarConfig
from ormar.models import ExcludableItems, Extra, Model
from ormar.models.metaclass import ModelMeta
from ormar.queryset import OrderAction, QuerySet, and_, or_ from ormar.queryset import OrderAction, QuerySet, and_, or_
from ormar.relations import RelationType from ormar.relations import RelationType
from ormar.signals import Signal from ormar.signals import Signal
@ -104,7 +103,6 @@ __all__ = [
"Float", "Float",
"ManyToMany", "ManyToMany",
"Model", "Model",
"Action",
"ModelDefinitionError", "ModelDefinitionError",
"MultipleMatches", "MultipleMatches",
"NoMatch", "NoMatch",
@ -119,8 +117,6 @@ __all__ = [
"ReferentialAction", "ReferentialAction",
"QuerySetProtocol", "QuerySetProtocol",
"RelationProtocol", "RelationProtocol",
"ModelMeta",
"property_field",
"post_bulk_update", "post_bulk_update",
"post_delete", "post_delete",
"post_save", "post_save",
@ -146,4 +142,5 @@ __all__ = [
"DECODERS_MAP", "DECODERS_MAP",
"LargeBinary", "LargeBinary",
"Extra", "Extra",
"OrmarConfig",
] ]

View File

@ -3,11 +3,10 @@ Module with all decorators that are exposed for users.
Currently only: Currently only:
* property_field - exposing @property like function as field in Model.dict()
* predefined signals decorators (pre/post + save/update/delete) * predefined signals decorators (pre/post + save/update/delete)
""" """
from ormar.decorators.property_field import property_field
from ormar.decorators.signals import ( from ormar.decorators.signals import (
post_bulk_update, post_bulk_update,
post_delete, post_delete,
@ -23,7 +22,6 @@ from ormar.decorators.signals import (
) )
__all__ = [ __all__ = [
"property_field",
"post_bulk_update", "post_bulk_update",
"post_delete", "post_delete",
"post_save", "post_save",

View File

@ -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

View File

@ -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 if TYPE_CHECKING: # pragma: no cover
from ormar import Model from ormar import Model
@ -34,7 +34,7 @@ def receiver(
else: else:
_senders = senders _senders = senders
for sender in _senders: for sender in _senders:
signals = getattr(sender.Meta.signals, signal) signals = getattr(sender.ormar_config.signals, signal)
signals.connect(func) signals.connect(func)
return func return func

View File

@ -15,11 +15,9 @@ class ModelDefinitionError(AsyncOrmException):
""" """
Raised for errors related to the model definition itself: 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 Field without required parameters
* defining a model with more than one primary_key * defining a model with more than one primary_key
* defining a model without primary_key * defining a model without primary_key
* setting primary_key column as pydantic_only
""" """
pass pass

Some files were not shown because too many files have changed in this diff Show More