pax_global_header 0000666 0000000 0000000 00000000064 15135204315 0014511 g ustar 00root root 0000000 0000000 52 comment=07c27e30bddfcd9683bef5ec2b06b07c18dbdabd
art049-odmantic-07c27e3/ 0000775 0000000 0000000 00000000000 15135204315 0014664 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.codecov.yml 0000664 0000000 0000000 00000000104 15135204315 0017102 0 ustar 00root root 0000000 0000000 codecov:
require_ci_to_pass: yes
notify:
after_n_builds: 14
art049-odmantic-07c27e3/.darglint 0000664 0000000 0000000 00000000144 15135204315 0016470 0 ustar 00root root 0000000 0000000 [darglint]
# Allow one line docstrings without arg spec
strictness = short
docstring_style = google
art049-odmantic-07c27e3/.devcontainer/ 0000775 0000000 0000000 00000000000 15135204315 0017423 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.devcontainer/Dockerfile 0000664 0000000 0000000 00000001017 15135204315 0021414 0 ustar 00root root 0000000 0000000 FROM mcr.microsoft.com/devcontainers/python:3.8
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
netcat-openbsd \
git-lfs \
&& apt-get clean autoclean \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& rm -f /var/cache/apt/archives/*.deb
# Install task
RUN curl -sL https://taskfile.dev/install.sh | sh
ENV PATH /root/.bin/:/root/.local/bin/:${PATH}
# Install devtools
RUN python3.8 -m pip install flit tox pre-commit
# Allow flit install as root
ENV FLIT_ROOT_INSTALL 1
art049-odmantic-07c27e3/.devcontainer/devcontainer.json 0000664 0000000 0000000 00000002057 15135204315 0023003 0 ustar 00root root 0000000 0000000 // Update the VARIANT arg in docker-compose.yml to pick a Node.js version: 10, 12, 14
{
"name": "Python3 & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"njpwerner.autodocstring",
"ryanluker.vscode-coverage-gutters",
"ms-python.python",
"ms-python.vscode-pylance",
"littlefoxteam.vscode-python-test-adapter",
"hbenl.vscode-test-explorer"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [8000, 8080, 27017],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bash -i -c 'task setup'"
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
// "remoteUser": "node"
}
art049-odmantic-07c27e3/.devcontainer/docker-compose.yml 0000664 0000000 0000000 00000001003 15135204315 0023052 0 ustar 00root root 0000000 0000000 version: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
db:
image: mongo:latest
restart: unless-stopped
volumes:
- mongodb-data:/data/db
volumes:
mongodb-data:
art049-odmantic-07c27e3/.gitattributes 0000664 0000000 0000000 00000000052 15135204315 0017554 0 ustar 00root root 0000000 0000000 *.png filter=lfs diff=lfs merge=lfs -text
art049-odmantic-07c27e3/.github/ 0000775 0000000 0000000 00000000000 15135204315 0016224 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15135204315 0020407 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.github/ISSUE_TEMPLATE/bug.md 0000664 0000000 0000000 00000001137 15135204315 0021510 0 ustar 00root root 0000000 0000000 ---
name: Bug
about: Create a bug report to help the project
labels: bug
---
# Bug
_A clear and concise description of what the bug is._
### Current Behavior
... _Steps to reproduce the bug_ ...
### Expected behavior
... _A clear and concise description of what you expected to happen._ ...
### Environment
- ODMantic version: ...
- MongoDB version: ...
- Pydantic infos (output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())`):
```
...
```
- Version of additional modules (if relevant):
- ...
**Additional context**
_Add any other context about the problem here._
art049-odmantic-07c27e3/.github/ISSUE_TEMPLATE/feature.md 0000664 0000000 0000000 00000001226 15135204315 0022365 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest a new idea for the project
labels: enhancement
---
# Feature request
### Context
_Is your feature request related to a problem? Please describe with a clear and concise description of what the problem is. Ex. I'm always frustrated when ..._
### Solution
_Describe the solution you'd like with a clear and concise description of what you want to happen._
#### Alternative solutions
_Describe alternatives you've considered with clear and concise description of any alternative solutions or features you've considered._
### Additional context
_Add any other context or screenshots about the feature request here._
art049-odmantic-07c27e3/.github/Taskfile.yml 0000664 0000000 0000000 00000002305 15135204315 0020511 0 ustar 00root root 0000000 0000000 version: "3"
silent: false
vars:
VERSION_FILE: ./__version__.txt
RELEASE_NOTE_FILE: ./__release_notes__.md
RELEASE_BRANCH: "master"
CURRENT_BRANCH:
sh: git rev-parse --symbolic-full-name --abbrev-ref HEAD
RELEASE_COMMIT_FILES: "pyproject.toml uv.lock CHANGELOG.md"
tasks:
default:
preconditions:
- sh: which gh
msg: gh not found
- sh: "[ {{.CURRENT_BRANCH}} = {{.RELEASE_BRANCH}} ]"
msg: "Please switch to {{.RELEASE_BRANCH}} to create a release"
cmds:
- task: prepare-workspace
- task: publish-release
- task: clean
prepare-workspace:
cmds:
- uv run .github/release.py
publish-release:
vars:
RELEASE_NOTE:
sh: cat {{.RELEASE_NOTE_FILE}}
NEW_VERSION:
sh: cat {{.VERSION_FILE}}
cmds:
- uv lock
- git add {{.RELEASE_COMMIT_FILES}}
- git commit -m "Release {{.NEW_VERSION}} 🚀"
- git push
- git tag v{{.NEW_VERSION}}
- git push --tags
- gh release create -d --target {{.RELEASE_BRANCH}} --title v{{.NEW_VERSION}} --notes-file {{.RELEASE_NOTE_FILE}} v{{.NEW_VERSION}}
clean:
cmds:
- rm -f {{.VERSION_FILE}}
- rm -f {{.RELEASE_NOTE_FILE}}
art049-odmantic-07c27e3/.github/dependabot.yml 0000664 0000000 0000000 00000000600 15135204315 0021050 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: uv
directory: /
schedule:
interval: monthly
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: ⬆️
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: ⬆️
art049-odmantic-07c27e3/.github/latest-changes.jinja2 0000664 0000000 0000000 00000000143 15135204315 0022223 0 ustar 00root root 0000000 0000000 - {{pr.title}} ([#{{pr.number}}]({{pr.html_url}}) by [@{{pr.user.login}}]({{pr.user.html_url}}))
art049-odmantic-07c27e3/.github/release.py 0000664 0000000 0000000 00000012017 15135204315 0020217 0 ustar 00root root 0000000 0000000 # /// script
# requires-python = ">=3.13"
# dependencies = [
# "semver>=2.13.0",
# "typer>=0.4.1",
# ]
# ///
import datetime
import os
import tomllib
from enum import Enum
import typer
from click.types import Choice
from semver import VersionInfo
class BumpType(str, Enum):
major = "major"
minor = "minor"
patch = "patch"
def get_current_version() -> VersionInfo:
with open("./pyproject.toml", "rb") as f:
pyproject = tomllib.load(f)
version = pyproject["project"]["version"]
return VersionInfo.parse(version)
def get_new_version(current_version: VersionInfo, bump_type: BumpType) -> VersionInfo:
if bump_type == BumpType.major:
return current_version.bump_major()
if bump_type == BumpType.minor:
return current_version.bump_minor()
if bump_type == BumpType.patch:
return current_version.bump_patch()
raise NotImplementedError("Unhandled bump type")
PYPROJECT_PATH = "./pyproject.toml"
def update_pyproject(current_version: VersionInfo, new_version: VersionInfo) -> None:
with open(PYPROJECT_PATH) as f:
content = f.read()
new_content = content.replace(
f'version = "{current_version}"', f'version = "{new_version}"'
)
if content == new_content:
typer.secho("Couldn't bump version in pyproject.toml", fg=typer.colors.RED)
raise typer.Exit(1)
with open(PYPROJECT_PATH, "w") as f:
f.write(new_content)
typer.secho("Version updated with success", fg=typer.colors.GREEN)
RELEASE_NOTE_PATH = "./__release_notes__.md"
def get_release_notes() -> str:
with open("./CHANGELOG.md", "r") as f:
while not f.readline().strip() == "## [Unreleased]":
pass
content = ""
while not (line := f.readline().strip()).startswith("## "):
content = content + line + "\n"
return content
def save_release_notes(release_notes: str) -> None:
if os.path.exists(RELEASE_NOTE_PATH):
typer.secho(
f"Release note file {RELEASE_NOTE_PATH} already exists", fg=typer.colors.RED
)
raise typer.Exit(1)
with open(RELEASE_NOTE_PATH, "w") as f:
f.write(release_notes)
typer.secho("Release note file generated with success", fg=typer.colors.GREEN)
CHANGELOG_PATH = "./CHANGELOG.md"
def update_changelog(current_version: VersionInfo, new_version: VersionInfo) -> None:
today = datetime.date.today()
date_str = f"{today.year}-{today.month:02d}-{today.day:02d}"
with open(CHANGELOG_PATH, "r") as f:
content = f.read()
# Add version header
content = content.replace(
"## [Unreleased]", ("## [Unreleased]\n\n" f"## [{new_version}] - {date_str}")
)
# Add version links
content = content.replace(
f"[unreleased]: https://github.com/art049/odmantic/compare/v{current_version}...HEAD",
(
f"[{new_version}]: https://github.com/art049/odmantic/compare/v{current_version}...v{new_version}\n"
f"[unreleased]: https://github.com/art049/odmantic/compare/v{new_version}...HEAD"
),
)
with open(CHANGELOG_PATH, "w") as f:
f.write(content)
typer.secho("Changelog updated with success", fg=typer.colors.GREEN)
VERSION_FILE_PATH = "__version__.txt"
def create_version_file(new_version: VersionInfo) -> None:
if os.path.exists(VERSION_FILE_PATH):
typer.secho(
f"Version file {VERSION_FILE_PATH} already exists", fg=typer.colors.RED
)
raise typer.Exit(1)
with open(VERSION_FILE_PATH, "w") as f:
f.write(str(new_version))
def summarize(
current_version: VersionInfo,
new_version: VersionInfo,
bump_type: BumpType,
release_notes: str,
) -> None:
typer.secho("Release summary:", fg=typer.colors.BLUE, bold=True)
typer.secho(f" Version bump: {bump_type.upper()}", bold=True)
typer.secho(f" Version change: {current_version} -> {new_version}", bold=True)
typer.confirm("Continue to release notes preview ?", abort=True, default=True)
release_header = typer.style(
f"RELEASE NOTE {new_version}\n\n", fg=typer.colors.BLUE, bold=True
)
typer.echo_via_pager(release_header + release_notes)
typer.confirm("Continue ?", abort=True, default=True)
def main() -> None:
current_version = get_current_version()
typer.secho(f"Current version: {current_version}", bold=True)
bump_type: BumpType = typer.prompt(
typer.style("Release type ?", fg=typer.colors.BLUE, bold=True),
type=Choice(list(BumpType.__members__)),
default=BumpType.patch.value,
show_choices=True,
)
new_version = get_new_version(current_version, bump_type)
release_notes = get_release_notes()
summarize(current_version, new_version, bump_type, release_notes)
save_release_notes(release_notes)
update_pyproject(current_version, new_version)
update_changelog(current_version, new_version)
create_version_file(new_version)
typer.confirm("Additionnal release commit files staged ?", abort=True, default=True)
if __name__ == "__main__":
typer.run(main)
art049-odmantic-07c27e3/.github/workflows/ 0000775 0000000 0000000 00000000000 15135204315 0020261 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.github/workflows/ci.yml 0000664 0000000 0000000 00000013656 15135204315 0021412 0 ustar 00root root 0000000 0000000 name: build
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: "0 2 * * *"
env:
UV_NO_SYNC: true
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- uses: pre-commit/action@v3.0.1
with:
extra_args: --all-files
compatibility-tests:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
pydantic-version:
- "2.5.2"
motor-version:
- "3.1.1"
- "3.2.0"
- "3.3.2"
steps:
- uses: actions/checkout@v6
- name: Mongo Service
run: bash mongodb-cluster/run.sh
env:
MODE: standalone
VERSION: "4.4"
- name: "Set up Python ${{ matrix.python-version }}"
uses: actions/setup-python@v6
with:
python-version: "${{ matrix.python-version }}"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install dependencies
run: |
uv sync --locked --no-dev --extra test
uv pip install "pymongo<4.9" "pydantic==${{ matrix.pydantic-version }}" "motor==${{ matrix.motor-version }}"
- name: Run compatibility checks.
run: |
uv run python -c "import motor; print(motor.version)" 1>&2
uv run python -c "import pydantic; print(pydantic.VERSION)" 1>&2
uv run python -m pytest -q -rs
tests:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
mongo-version:
- "4.4"
- "5"
- "6"
mongo-mode:
- standalone
connection-string:
- mongodb://localhost:27017/
include:
- python-version: "3.12"
mongo-version: 4.0
mongo-mode: replicaSet
connection-string: mongodb://172.16.17.11:27017,172.16.17.12:27017,172.16.17.13:27017/?replicaSet=mongodb-action-replica-set
- python-version: "3.12"
mongo-version: 4.2
mongo-mode: sharded
connection-string: mongodb://172.16.17.10:27017/?retryWrites=false
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Mongo Service
run: bash mongodb-cluster/run.sh
env:
MODE: ${{ matrix.mongo-mode }}
VERSION: ${{ matrix.mongo-version }}
- name: Install dependencies
run: |
uv sync --locked --no-dev --extra test
uv pip install flit
- name: Run all tests
run: |
set -e
uv run coverage run -m pytest -v
uv run coverage report -m
uv run coverage xml
env:
TEST_MONGO_URI: ${{ matrix.connection-string }}
TEST_MONGO_MODE: ${{ matrix.mongo-mode }}
- uses: codecov/codecov-action@v5
if: github.event_name != 'schedule' # Don't report coverage for nightly builds
with:
flags: tests-${{ matrix.python-version }}-${{ matrix.mongo-version }}-${{ matrix.mongo-mode }}
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
integrated-realworld-test:
runs-on: ubuntu-latest
env:
UV_SYSTEM_PYTHON: true
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
path: odmantic-current
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- uses: actions/checkout@v6
with:
repository: art049/fastapi-odmantic-realworld-example
submodules: recursive
path: fastapi-odmantic-realworld-example
- name: Set up Python 3.12
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install poetry and flit
run: |
uv pip install poetry
uv pip install flit
- name: Mongo Service
run: bash odmantic-current/mongodb-cluster/run.sh
- name: Install dependencies (w/o ODMantic)
working-directory: fastapi-odmantic-realworld-example
run: |
echo "$(grep -v 'odmantic =' ./pyproject.toml)" > pyproject.toml
poetry lock
poetry install --no-root
- name: Install current ODMantic version
working-directory: fastapi-odmantic-realworld-example
run: poetry run pip install ../odmantic-current/
- name: Start the FastAPI server
working-directory: fastapi-odmantic-realworld-example
run: |
./scripts/start.sh &
# Wait for the server
while ! curl "http://localhost:8000/health" > /dev/null 2>&1
do
sleep 1;
done
echo "Server ready."
env:
MONGO_URI: "mongodb://localhost:27017/"
- name: Run realworld backend tests
working-directory: fastapi-odmantic-realworld-example
run: ./realworld/api/run-api-tests.sh
env:
APIURL: http://localhost:8000
all-ci-checks:
needs:
- static-analysis
- compatibility-tests
- tests
- integrated-realworld-test
runs-on: ubuntu-latest
steps:
- run: echo "All CI checks passed."
art049-odmantic-07c27e3/.github/workflows/codspeed.yml 0000664 0000000 0000000 00000002317 15135204315 0022575 0 ustar 00root root 0000000 0000000 name: CodSpeed
on:
# Run on pushes to the main branch
push:
branches:
- "master" # or "main"
# Run on pull requests
pull_request:
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:
env:
UV_NO_SYNC: true
jobs:
benchmarks:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Mongo Service
run: bash mongodb-cluster/run.sh
env:
MODE: sharded
VERSION: "4.2"
- name: Install dependencies
run: |
uv sync --locked --no-dev --extra test
- name: Run benches
uses: CodSpeedHQ/action@v4
with:
mode: simulation
run: uv run pytest tests/integration/benchmarks --codspeed
env:
TEST_MONGO_URI: "mongodb://172.16.17.10:27017/?retryWrites=false"
TEST_MONGO_MODE: "sharded"
art049-odmantic-07c27e3/.github/workflows/docs-preview.yml 0000664 0000000 0000000 00000002505 15135204315 0023415 0 ustar 00root root 0000000 0000000 name: docs-preview
on:
- pull_request
env:
UV_NO_SYNC: true
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
lfs: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install dependencies
run: |
uv sync --locked --no-dev --extra doc
- name: Build documentation
run: uv run mkdocs build -f ./mkdocs.yml
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1.1
id: deployment
with:
publish-dir: "./site"
production-branch: master
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "#${{ github.event.number }}: ${{ github.event.pull_request.title }}"
enable-pull-request-comment: true
enable-commit-comment: false
overwrites-pull-request-comment: true
alias: docs-preview-${{ github.event.number }}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 1
art049-odmantic-07c27e3/.github/workflows/docs.yml 0000664 0000000 0000000 00000001604 15135204315 0021735 0 ustar 00root root 0000000 0000000 name: docs
on:
release:
types:
- published
- released
- edited
workflow_dispatch:
env:
UV_NO_SYNC: true
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
lfs: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install dependencies
run: |
uv sync --locked --no-dev --extra doc
- name: Build documentation
run: uv run mkdocs build -f ./mkdocs.yml
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
art049-odmantic-07c27e3/.github/workflows/latest-changes.yml 0000664 0000000 0000000 00000001307 15135204315 0023707 0 ustar 00root root 0000000 0000000 name: latest-changes
on:
pull_request_target:
branches:
- master
types:
- closed
workflow_dispatch:
inputs:
number:
description: PR number
required: true
jobs:
latest-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.GH_W_TOKEN }}
- name: Disable LFS hooks
run: rm .git/hooks/post-commit .git/hooks/pre-push
- uses: tiangolo/latest-changes@0.4.1
with:
token: ${{ secrets.GH_W_TOKEN }}
template_file: ./.github/latest-changes.jinja2
latest_changes_file: ./CHANGELOG.md
latest_changes_header: '## \[Unreleased\]\n\n'
art049-odmantic-07c27e3/.github/workflows/release.yml 0000664 0000000 0000000 00000001122 15135204315 0022420 0 ustar 00root root 0000000 0000000 name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
main:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Build the package
run: uv build
- name: Publish
run: uv publish
art049-odmantic-07c27e3/.gitignore 0000664 0000000 0000000 00000002377 15135204315 0016665 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
poetry.lock
.task
__release_notes__.md
__version__.txt
.testmondata
.codspeed/
art049-odmantic-07c27e3/.mongodb-cluster-action/ 0000775 0000000 0000000 00000000000 15135204315 0021321 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.pre-commit-config.yaml 0000664 0000000 0000000 00000002742 15135204315 0021152 0 ustar 00root root 0000000 0000000 # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_language_version:
python: python3.12
node: 15.4.0
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: "^.github/latest-changes.jinja2"
- id: check-yaml
exclude: "^mkdocs.yml"
- id: check-added-large-files
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.2.1
hooks:
- id: prettier
exclude: "^docs/.*"
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1
hooks:
- id: mypy
exclude: "^docs/.*"
additional_dependencies:
- pydantic>=2.0.0
- motor~=3.0.0
- types-pytz~=2022.1.1
args: [--no-pretty, --show-error-codes]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.3
hooks:
- id: ruff
exclude: "^docs/.*|.github/release.py"
- id: ruff-format
exclude: "^docs/.*|.github/release.py"
- repo: https://github.com/pycqa/pydocstyle
rev: 6.1.1 # pick a git hash / tag to point to
hooks:
- id: pydocstyle
files: "^odmantic/"
additional_dependencies:
- toml
- repo: https://github.com/terrencepreilly/darglint
rev: v1.8.1
hooks:
- id: darglint
files: "^odmantic/"
stages: [] # Only run in CI with --all since it's slow
art049-odmantic-07c27e3/.prettierignore 0000664 0000000 0000000 00000000032 15135204315 0017722 0 ustar 00root root 0000000 0000000 docs/**/*.md
CHANGELOG.md
art049-odmantic-07c27e3/.vscode/ 0000775 0000000 0000000 00000000000 15135204315 0016225 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/.vscode/extensions.json 0000664 0000000 0000000 00000000412 15135204315 0021314 0 ustar 00root root 0000000 0000000 {
"recommendations": [
"njpwerner.autodocstring",
"ryanluker.vscode-coverage-gutters",
"ms-python.python",
"ms-python.vscode-pylance",
"littlefoxteam.vscode-python-test-adapter",
"hbenl.vscode-test-explorer",
"charliermarsh.ruff"
]
}
art049-odmantic-07c27e3/.vscode/launch.json 0000664 0000000 0000000 00000001054 15135204315 0020372 0 ustar 00root root 0000000 0000000 {
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "internalConsole"
},
{
"name": "Debug Tests",
"type": "python",
"request": "test",
"console": "internalConsole",
"justMyCode": false
}
]
}
art049-odmantic-07c27e3/.vscode/settings.json 0000664 0000000 0000000 00000000764 15135204315 0020767 0 ustar 00root root 0000000 0000000 {
"editor.rulers": [88],
"python.envFile": "${workspaceFolder}/.env",
"python.pythonPath": "${workspaceFolder}/.venv/bin/python3.8",
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
"python.testing.pytestEnabled": true,
"editor.formatOnSave": true,
"files.exclude": {
".venv/": false,
".pytest_cache/": true,
".mypy_cache/": true
},
"python.languageServer": "Pylance",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
art049-odmantic-07c27e3/CHANGELOG.md 0000664 0000000 0000000 00000057107 15135204315 0016507 0 ustar 00root root 0000000 0000000 # Changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.1.0] - 2026-01-24
#### Features
- ✨ Add support for Python 3.10 Union syntax, like: `name: str | None = None` ([#501](https://github.com/art049/odmantic/pull/501) by [@rhyn0](https://github.com/rhyn0))
## [1.0.3] - 2026-01-21
#### Fixes
- 🐛 Fix compatibility with latest Pydantic, upgrade locked dependencies with uv ([#522](https://github.com/art049/odmantic/pull/522) by [@tiangolo](https://github.com/tiangolo))
- Access `model_fields` on class and not instance ([#510](https://github.com/art049/odmantic/pull/510) by [@sydney-runkle](https://github.com/sydney-runkle))
#### Internal
- 📝 Update release notes ([#541](https://github.com/art049/odmantic/pull/541) by [@tiangolo](https://github.com/tiangolo))
- 👷 Update release CI, use uv ([#520](https://github.com/art049/odmantic/pull/520) by [@tiangolo](https://github.com/tiangolo))
- 👷 Fix CI ([#519](https://github.com/art049/odmantic/pull/519) by [@tiangolo](https://github.com/tiangolo))
- 👷 Make pre-commit run with Python 3.12 in CI ([#539](https://github.com/art049/odmantic/pull/539) by [@tiangolo](https://github.com/tiangolo))
- ⬆️ Add Python 3.12 to CI ([#538](https://github.com/art049/odmantic/pull/538) by [@tiangolo](https://github.com/tiangolo))
- ⬆️ Unpin dev dependencies, use uv.lock and upgrade ([#536](https://github.com/art049/odmantic/pull/536) by [@tiangolo](https://github.com/tiangolo))
- 👷 Remove not used config in Codcov ([#525](https://github.com/art049/odmantic/pull/525) by [@tiangolo](https://github.com/tiangolo))
- 👷 Update dependabot, set up Dependabot for GitHub Actions ([#524](https://github.com/art049/odmantic/pull/524) by [@tiangolo](https://github.com/tiangolo))
- 👷 Update GitHub Actions versions ([#523](https://github.com/art049/odmantic/pull/523) by [@tiangolo](https://github.com/tiangolo))
- 👷 Upgrade GitHub Action latest-changes ([#521](https://github.com/art049/odmantic/pull/521) by [@tiangolo](https://github.com/tiangolo))
## [1.0.2] - 2024-04-26
### Fixed
- fix: support pydantic 2.7 ([#462](https://github.com/art049/odmantic/pull/462) by [@adriencaccia](https://github.com/adriencaccia))
### Internals
- chore(bench): update CodSpeed/action to v2 ([#461](https://github.com/art049/odmantic/pull/461) by [@adriencaccia](https://github.com/adriencaccia))
- Fix dev container environment ([#438](https://github.com/art049/odmantic/pull/438) by [@Kludex](https://github.com/Kludex) and [@art049](https://github.com/art049))
## [1.0.1] - 2024-03-18
### Fixed
- Optional and Union generic types definition issues ([#416](https://github.com/art049/odmantic/pull/416) by [@netomi](https://github.com/netomi))
- Remove continuously changing example for DateTime objects ([#406](https://github.com/art049/odmantic/pull/406) by [@Mokto](https://github.com/Mokto))
### Added
- Support the `examples` property in Field descriptors ([#404](https://github.com/art049/odmantic/pull/404) by [@Mokto](https://github.com/Mokto))
### Internals
- Fix Pydantic docs URLs ([#366](https://github.com/art049/odmantic/pull/366) by [@aminalaee](https://github.com/aminalaee))
- Add a test with a model when defining multiple optional fields ([#426](https://github.com/art049/odmantic/pull/426) by [@art049](https://github.com/art049))
- Bump ruff and use ruff format ([#425](https://github.com/art049/odmantic/pull/425) by [@art049](https://github.com/art049))
## [1.0.0] - 2023-12-13
I'm excited to announce ODMantic v1.0.0, with Pydantic v2 support! 🎉
This release brings a range of changes that are aligned with the new Pydantic architecture.
Keeping a maintainable and healthy codebase was especially important.
Thus from now on, ODMantic will not support Python 3.7, Pydantic V1, and Motor 2.x anymore.
Overall, integrating with Pydantic v2 brings around **30% performance improvements** on
common operations. ⚡️⚡️⚡️
We have a lot of room to improve the performance further now that we only support Pydantic v2.
There is also a 300% 👀 improvement on the bulk saves crafted by @tiangolo that will be merged soon! 🚀
Special thanks to @tiangolo for his help on this release and for saving me a lot of time
figuring out a particularly annoying bug!
Check out the **[migration guide](https://art049.github.io/odmantic/migration_guide/)** to
upgrade your codebase and enjoy this new release!
### Breaking changes
- Support for Python 3.7, Pydantic v1 and Motor 2.x has been dropped
- `Optional[T]` doesn't have a `None` implicit default value anymore
- `Model.copy` doesn't support the `exclude` and `include` kwargs anymore
- `odmantic.Field` doesn't accept extra kwargs anymore since it's slated to be removed in Pydantic
- The `Config` class is no longer supported and the `model_config` dict should be used instead
- `DocumentParsingError` is no longer a subclass of `ValidationError`
- The `__bson__` class method is no longer supported to define BSON encoders on custom types. The new method to customize BSON encoding is to use the `WithBSONSerializer` annotation.
- Decimals (`decimal.Decimal` and `bson.Decimal128`) are now serialized as strings in JSON documents
- Custom JSON encoders(defined with the `json_encoders` config option) are no longer effective on `odmantic.bson` types. Annotated types with `pydantic.PlainSerializer` should be used instead.
### Removals
- `AIOEngineDependency` has been removed since it was deprecated in v0.2.0 in favor of a global engine object
- Defining the collection with `__collection__` has been removed since it was deprecated in v0.3.0 in favor of the `collection` config option
### Deprecations
_We comply with the new Pydantic method naming, prefixing them with `model_`_
- `Model.dict` has been deprecated in favor of `Model.model_dump`
- `Model.doc` has been deprecated in favor of `Model.model_dump_doc`
- `Model.parse_doc` has been deprecated in favor of `Model.model_validate_doc`
- `Model.update` has been deprecated in favor of `Model.model_update`
- `Model.copy` has been deprecated in favor of `Model.model_copy`
---
#### Details
- Integrate with Pydantic V2([#361](https://github.com/art049/odmantic/pull/361) and [#377](https://github.com/art049/odmantic/pull/377) by [@art049](https://github.com/art049))
- Update CI to use GitHub Actions matrix instead of tox, upgrade minimum Pydantic to 1.10.8 as needed by the tests ([#376](https://github.com/art049/odmantic/pull/376) by [@tiangolo](https://github.com/tiangolo))
- Add benchmarks on common sync operations ([#362](https://github.com/art049/odmantic/pull/362) by [@art049](https://github.com/art049))
## [0.9.2] - 2023-01-03
### Fixed
- Properly handle literals among generic types ([#313](https://github.com/art049/odmantic/pull/313) by [@art049](https://github.com/art049))
### Internals
- Pin tox to fix CI ([#308](https://github.com/art049/odmantic/pull/308) by [@tiangolo](https://github.com/tiangolo))
## [0.9.1] - 2022-11-24
### Fixed
- Bump motor version ([#296](https://github.com/art049/odmantic/pull/296) by [@valeriiduz](https://github.com/valeriiduz))
### Internals
- Migrate to ruff ([#299](https://github.com/art049/odmantic/pull/299) by [@art049](https://github.com/art049))
## [0.9.0] - 2022-09-25
#### Added
- Create new generic types to support generic collection types ([#240](https://github.com/art049/odmantic/pull/240) by [@erny](https://github.com/erny) & [@art049](https://github.com/art049))
Thus, it's now possible to define models like this in python **3.9+** 🚀:
```python
class User(Model):
scopes: list[str]
friendsIds: list[ObjectId]
skills: set[str]
```
- Allow using generators with `in_` and `not_in` ([#270](https://github.com/art049/odmantic/pull/270) by [@art049](https://github.com/art049))
#### Fixed
- Fix `EmbeddedModel` generics definition with a custom `key_name` ([#269](https://github.com/art049/odmantic/pull/269) by [@art049](https://github.com/art049))
- Raise a `TypeError` when defining a `Reference` in a generic(List, Dict, Tuple, ...) containing EmbeddedModels ([#269](https://github.com/art049/odmantic/pull/269) by [@art049](https://github.com/art049))
## [0.8.0] - 2022-09-09
#### Added
- Allow Index definition ([feature documentation](https://art049.github.io/odmantic/modeling/#indexes)) ([#255](https://github.com/art049/odmantic/pull/255) by [@art049](https://github.com/art049))
- Allow using the `Config.extra` attribute from pydantic ([#259](https://github.com/art049/odmantic/pull/259) by [@art049](https://github.com/art049))
#### Fixed
- Fix embedded models parsing with custom `key_name` ([#262](https://github.com/art049/odmantic/pull/262) by [@iXB3](https://github.com/iXB3))
- Fix `engine.save` using an embedded model as a primary key ([#258](https://github.com/art049/odmantic/pull/258) by [@art049](https://github.com/art049))
- Fix engine creation typo in the documentation ([#257](https://github.com/art049/odmantic/pull/257) by [@art049](https://github.com/art049))
## [0.7.1] - 2022-09-02
#### Fixed
- Fix dataclass transform constructor type hints ([#249](https://github.com/art049/odmantic/pull/249) by [@art049](https://github.com/art049))
#### Internals
- Update Mongo version in the CI build matrix ([#247](https://github.com/art049/odmantic/pull/247) by [@art049](https://github.com/art049))
## [0.7.0] - 2022-08-30
#### Added
- Add new SyncEngine, support async and sync code ([#231](https://github.com/art049/odmantic/pull/231) by [@tiangolo](https://github.com/tiangolo))
- Sync engine documentation ([#238](https://github.com/art049/odmantic/pull/238) by [@art049](https://github.com/art049))
- Friendly interface for session and transactions ([#244](https://github.com/art049/odmantic/pull/244) by [@art049](https://github.com/art049))
- Implement the `engine.remove` method to allow instance deletion from a query ([#147](https://github.com/art049/odmantic/pull/147) & [#237](https://github.com/art049/odmantic/pull/237) by [@joeriddles](https://github.com/joeriddles) & [@art049](https://github.com/art049))
#### Internals
- Remove unnecessary Python 3.6 type fixes ([#243](https://github.com/art049/odmantic/pull/243) by [@art049](https://github.com/art049))
- Switch Mongo action to art049/mongodb-cluster-action ([#245](https://github.com/art049/odmantic/pull/245) by [@art049](https://github.com/art049))
- Add Realworld API integrated test ([#246](https://github.com/art049/odmantic/pull/246) by [@art049](https://github.com/art049))
## [0.6.0] - 2022-08-24
#### Breaking Changes
- Drop support for Python 3.6 ([#230](https://github.com/art049/odmantic/pull/230) by [@tiangolo](https://github.com/tiangolo))
#### Added
- Upgrade types and add support for instance autocompletion with `dataclass_transform` (VS Code autocompletion) ([#230](https://github.com/art049/odmantic/pull/230) by [@tiangolo](https://github.com/tiangolo))
- Support Python 3.10 ([#235](https://github.com/art049/odmantic/pull/235) by [@art049](https://github.com/art049))
#### Fixed
- Fix using the shared session when updating a document ([#227](https://github.com/art049/odmantic/pull/227) by [@tiangolo](https://github.com/tiangolo))
- Fix `EmbeddedModel` deep copy mutability ([#239](https://github.com/art049/odmantic/pull/239) by [@art049](https://github.com/art049))
- Allow models to contain string-based datetime fields that indicate UTC ([#136](https://github.com/art049/odmantic/pull/136) by [@kfox](https://github.com/kfox))
- Fix `Reference` usage with non the non default primary key ([#184](https://github.com/art049/odmantic/pull/184) by [@dynalz](https://github.com/dynalz))
- Fix `key_name` use on EmbeddedModels ([#195](https://github.com/art049/odmantic/pull/195) by [@jvanegmond](https://github.com/jvanegmond))
#### Internals
- Support Python 3.10 in tox ([#236](https://github.com/art049/odmantic/pull/236) by [@art049](https://github.com/art049))
- Fix missing f string in an exception message ([#222](https://github.com/art049/odmantic/pull/222) by [@voglster](https://github.com/voglster))
- Finalize flit migration ([#232](https://github.com/art049/odmantic/pull/232) by [@art049](https://github.com/art049))
## [0.5.0] - 2022-06-01
- Support motor 3.0 ([#224](https://github.com/art049/odmantic/pull/224) by [@art049](https://github.com/art049))
- Support pydantic 1.9.0 ([#218](https://github.com/art049/odmantic/pull/218) by [@art049](https://github.com/art049))
## [0.4.0] - 2022-04-23
#### Added
- Update and copy methods:
- Updating multiple fields at once is now directly possible from a pydantic model or
a dictionary ([feature documentation](https://art049.github.io/odmantic/engine/#patching-multiple-fields-at-once),
[sample use case with FastAPI](https://art049.github.io/odmantic/usage_fastapi/#updating-a-tree))
- Changing the primary field of an instance is now easier
([documentation](https://art049.github.io/odmantic/engine/#changing-the-primary-field))
- Patch and copy Model instances ([#39](https://github.com/art049/odmantic/pull/39) by [@art049](https://github.com/art049))
#### Fixed
- Update example in README ([#192](https://github.com/art049/odmantic/pull/192) by [@jasper-moment](https://github.com/jasper-moment))
- Update README.md ([#129](https://github.com/art049/odmantic/pull/129) by [@Kludex](https://github.com/Kludex))
#### Internals
- ⬆️ Update motor requirement from >=2.1.0,<2.5.0 to >=2.1.0,<2.6.0 ([#160](https://github.com/art049/odmantic/pull/160) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update typer requirement from ^0.3.2 to ^0.4.1 ([#214](https://github.com/art049/odmantic/pull/214) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update mypy requirement from ^0.910 to ^0.942 ([#215](https://github.com/art049/odmantic/pull/215) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update fastapi requirement from >=0.61.1,<0.67.0 to >=0.61.1,<0.69.0 ([#166](https://github.com/art049/odmantic/pull/166) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update fastapi requirement from >=0.61.1,<0.64.0 to >=0.61.1,<0.67.0 ([#150](https://github.com/art049/odmantic/pull/150) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update mypy requirement from ^0.812 to ^0.910 ([#142](https://github.com/art049/odmantic/pull/142) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update pytest-asyncio requirement from ^0.14.0 to ^0.15.0 ([#125](https://github.com/art049/odmantic/pull/125) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update motor requirement from >=2.1.0,<2.4.0 to >=2.1.0,<2.5.0 ([#124](https://github.com/art049/odmantic/pull/124) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update importlib-metadata requirement from >=1,<4 to >=1,<5 ([#126](https://github.com/art049/odmantic/pull/126) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update pydocstyle requirement from ^5.1.1 to ^6.0.0 ([#119](https://github.com/art049/odmantic/pull/119) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update isort requirement from ~=5.7.0 to ~=5.8.0 ([#122](https://github.com/art049/odmantic/pull/122) by [@dependabot[bot]](https://github.com/apps/dependabot))
- ⬆️ Update flake8 requirement from ~=3.8.4 to ~=3.9.0 ([#116](https://github.com/art049/odmantic/pull/116) by [@dependabot[bot]](https://github.com/apps/dependabot))
## [0.3.5] - 2021-05-12
#### Security
- Change allowed pydantic versions to handle [CVE-2021-29510](https://github.com/samuelcolvin/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) by [@art049](https://github.com/art049)
## [0.3.4] - 2021-03-04
#### Fixed
- Fix modified mark clearing on save for nested models ([#88](https://github.com/art049/odmantic/pull/88) by [@Olegt0rr](https://github.com/Olegt0rr))
- Don't replace default field description for ObjectId ([#82](https://github.com/art049/odmantic/pull/82) by [@Olegt0rr](https://github.com/Olegt0rr))
#### Internals
- Support pydantic 1.8 ([#113](https://github.com/art049/odmantic/pull/113) by [@art049](https://github.com/art049))
- Add nightly builds ([#114](https://github.com/art049/odmantic/pull/114) by [@art049](https://github.com/art049))
- CI Matrix with Standalone instances, ReplicaSets and Sharded clusters ([#91](https://github.com/art049/odmantic/pull/91) by [@art049](https://github.com/art049))
- Update mkdocstrings requirement from ^0.14.0 to ^0.15.0 ([#110](https://github.com/art049/odmantic/pull/110) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update mkdocs-material requirement from ^6.0.2 to ^7.0.3 ([#111](https://github.com/art049/odmantic/pull/111) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update mypy requirement from ^0.800 to ^0.812 ([#106](https://github.com/art049/odmantic/pull/106) by [@dependabot[bot]](https://github.com/apps/dependabot))
## [0.3.3] - 2021-02-13
#### Fixed
- Remove `bypass_document_validation` save option to avoid `Not Authorized` errors ([#85](https://github.com/art049/odmantic/pull/85) by [@Olegt0rr](https://github.com/Olegt0rr))
- Fix microseconds issue: use truncation instead of round ([#100](https://github.com/art049/odmantic/pull/100) by [@erny](https://github.com/erny))
- Add py.typed to ship typing information for mypy ([#101](https://github.com/art049/odmantic/pull/101) by [@art049](https://github.com/art049))
- Fix datetime field default example value naiveness ([#103](https://github.com/art049/odmantic/pull/103) by [@art049](https://github.com/art049))
#### Internals
- Update pytz requirement from ^2020.1 to ^2021.1 ([#98](https://github.com/art049/odmantic/pull/98) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update mkdocstrings requirement from ^0.13.2 to ^0.14.0 ([#92](https://github.com/art049/odmantic/pull/92) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update mypy requirement from ^0.790 to ^0.800 ([#97](https://github.com/art049/odmantic/pull/97) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update isort requirement from ~=5.6.4 to ~=5.7.0 ([#90](https://github.com/art049/odmantic/pull/90) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update fastapi requirement from >=0.61.1,<0.63.0 to >=0.61.1,<0.64.0 ([#84](https://github.com/art049/odmantic/pull/84) by [@dependabot[bot]](https://github.com/apps/dependabot))
## [0.3.2] - 2020-12-15
#### Added
- Fix embedded model field update ([#77](https://github.com/art049/odmantic/pull/77) by [@art049](https://github.com/art049))
- Fix `datetime` bson inheritance issue ([#78](https://github.com/art049/odmantic/pull/78) by [@Olegt0rr](https://github.com/Olegt0rr))
#### Internals
- Migrate to the updated prettier precommit hook ([#74](https://github.com/art049/odmantic/pull/74) by [@art049](https://github.com/art049))
- Fix tox dependency install ([#72](https://github.com/art049/odmantic/pull/72) by [@art049](https://github.com/art049))
- Update uvicorn requirement from ^0.12.1 to ^0.13.0 ([#67](https://github.com/art049/odmantic/pull/67) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update mypy requirement from ^0.782 to ^0.790 ([#48](https://github.com/art049/odmantic/pull/48) by [@dependabot[bot]](https://github.com/apps/dependabot-preview))
- Update importlib-metadata requirement from ^1.0 to >=1,<4 ([#54](https://github.com/art049/odmantic/pull/54) by [@dependabot[bot]](https://github.com/apps/dependabot))
- Update flake8 requirement from ==3.8.3 to ==3.8.4 ([#47](https://github.com/art049/odmantic/pull/47) by [@dependabot[bot]](https://github.com/apps/dependabot-preview))
- Update fastapi requirement from ^0.61.1 to >=0.61.1,<0.63.0 ([#59](https://github.com/art049/odmantic/pull/59) by [@dependabot[bot]](https://github.com/apps/dependabot))
## [0.3.1] - 2020-11-16
#### Added
- Add `schema_extra` config option ([#41](https://github.com/art049/odmantic/pull/41) by [@art049](https://github.com/art049))
#### Fixed
- Fix `setattr` error on a manually initialized EmbeddedModel ([#40](https://github.com/art049/odmantic/pull/40) by [@art049](https://github.com/art049))
## [0.3.0] - 2020-11-09
#### Deprecated
- Deprecate usage of `__collection__` to customize the collection name. Prefer the
`collection` Config option ([more
details](https://art049.github.io/odmantic/modeling/#collection))
#### Added
- Allow parsing document with unset fields defaults ([documentation](https://art049.github.io/odmantic/raw_query_usage/#advanced-parsing-behavior)) ([#28](https://github.com/art049/odmantic/pull/28) by [@art049](https://github.com/art049))
- Integration with Pydantic `Config` class ([#37](https://github.com/art049/odmantic/pull/37) by [@art049](https://github.com/art049)):
- It's now possible to define custom `json_encoders` on the Models
- Some other `Config` options provided by Pydantic are now available ([more
details](https://art049.github.io/odmantic/modeling/#advanced-configuration))
- Support CPython 3.9 ([#32](https://github.com/art049/odmantic/pull/32) by
[@art049](https://github.com/art049))
- Unpin pydantic to support 1.7.0 ([#29](https://github.com/art049/odmantic/pull/29) by
[@art049](https://github.com/art049))
## [0.2.1] - 2020-10-25
#### Fixed
- Fix combined use of `skip` and `limit` with `engine.find` (#25 by @art049)
## [0.2.0] - 2020-10-25
#### Deprecated
- Deprecate `AIOEngineDependency` to prefer a global engine object, [more
details](https://art049.github.io/odmantic/usage_fastapi/#building-the-engine) (#21 by
@art049)
#### Added
- [Add sorting support](https://art049.github.io/odmantic/querying/#sorting) (#17 by @adriencaccia)
- Support motor 2.3.0 (#20 by @art049)
#### Fixed
- Fix FastAPI usage with References (#19 by @art049)
#### Docs
- Adding a CONTRIBUTING.md file to the root directory with link to docs (#8 by @sanders41)
- Raw Query Usage Documentation Fix (#10 by @adeelsohailahmed)
- Update Filtering to include Bitwise Operator Warning (#24 by @adeelsohailahmed)
## [0.1.0] - 2020-10-19
#### Initial Release
[0.1.0]: https://github.com/art049/odmantic/releases/tag/v0.1.0
[0.2.0]: https://github.com/art049/odmantic/compare/v0.1.0...v0.2.0
[0.2.1]: https://github.com/art049/odmantic/compare/v0.2.0...v0.2.1
[0.3.0]: https://github.com/art049/odmantic/compare/v0.2.1...v0.3.0
[0.3.1]: https://github.com/art049/odmantic/compare/v0.3.0...v0.3.1
[0.3.2]: https://github.com/art049/odmantic/compare/v0.3.1...v0.3.2
[0.3.3]: https://github.com/art049/odmantic/compare/v0.3.2...v0.3.3
[0.3.4]: https://github.com/art049/odmantic/compare/v0.3.3...v0.3.4
[0.3.5]: https://github.com/art049/odmantic/compare/v0.3.4...v0.3.5
[0.4.0]: https://github.com/art049/odmantic/compare/v0.3.5...v0.4.0
[0.5.0]: https://github.com/art049/odmantic/compare/v0.4.0...v0.5.0
[0.6.0]: https://github.com/art049/odmantic/compare/v0.5.0...v0.6.0
[0.7.0]: https://github.com/art049/odmantic/compare/v0.6.0...v0.7.0
[0.7.1]: https://github.com/art049/odmantic/compare/v0.7.0...v0.7.1
[0.8.0]: https://github.com/art049/odmantic/compare/v0.7.1...v0.8.0
[0.9.0]: https://github.com/art049/odmantic/compare/v0.8.0...v0.9.0
[0.9.1]: https://github.com/art049/odmantic/compare/v0.9.0...v0.9.1
[0.9.2]: https://github.com/art049/odmantic/compare/v0.9.1...v0.9.2
[1.0.0]: https://github.com/art049/odmantic/compare/v0.9.2...v1.0.0
[1.0.1]: https://github.com/art049/odmantic/compare/v1.0.0...v1.0.1
[1.0.2]: https://github.com/art049/odmantic/compare/v1.0.1...v1.0.2
[1.0.3]: https://github.com/art049/odmantic/compare/v1.0.2...v1.0.3
[1.1.0]: https://github.com/art049/odmantic/compare/v1.0.3...v1.1.0
[unreleased]: https://github.com/art049/odmantic/compare/v1.1.0...HEAD
art049-odmantic-07c27e3/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000012147 15135204315 0017470 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
arty049@protonmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
art049-odmantic-07c27e3/CONTRIBUTING.md 0000664 0000000 0000000 00000000165 15135204315 0017117 0 ustar 00root root 0000000 0000000 Please see the [contributing guidelines](https://art049.github.io/odmantic/contributing/) on the documentation site.
art049-odmantic-07c27e3/LICENSE 0000664 0000000 0000000 00000001351 15135204315 0015671 0 ustar 00root root 0000000 0000000 ISC License
Copyright (c) 2020, Arthur Pastel
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
art049-odmantic-07c27e3/README.md 0000664 0000000 0000000 00000023334 15135204315 0016150 0 ustar 00root root 0000000 0000000 ODMantic
[](https://github.com/art049/odmantic/actions/workflows/ci.yml)
[](https://codecov.io/gh/art049/odmantic)

[](https://pypi.org/project/odmantic)
[](https://codspeed.io/art049/odmantic)
---
**Documentation**: [https://art049.github.io/odmantic/](https://art049.github.io/odmantic/)
---
Sync and Async ODM (Object Document Mapper) for MongoDB based on standard Python type hints. Built on top of Pydantic for model
definition and validation.
Core features:
- **Simple**: define your model by typing your fields using Python types, build queries
using Python comparison operators
- **Developer experience**: field/method autocompletion, type hints, data validation,
performing database operations with a functional API
- **Fully typed**: leverage static analysis to reduce runtime issues
- **AsyncIO support**: works well with ASGI frameworks (FastAPI, quart, sanic, Starlette, ...) but works also perfectly in synchronous environments
- **Serialization**: built-in JSON serialization and JSON schema generation
## Requirements
**Python**: 3.8 and later (tested against 3.8, 3.9, 3.10 and 3.11)
**Pydantic**: 2.5 and later
**MongoDB**: 4.0 and later
## Installation
```shell
pip install odmantic
```
## Example
> To enjoy an async context without any code boilerplate, you can reproduce the
> following steps using the AsyncIO REPL (only for Python 3.8+).
>
> ```
> python3.8 -m asyncio
> ```
>
> If you are using an earlier version of Python, you can use href="https://ipython.readthedocs.io/en/stable/install/index.html"
> target="_blank">IPython which provide an Autoawait feature (starting from Python
> 3.6).
### Define your first model
```python
from typing import Optional
from odmantic import Field, Model
class Publisher(Model):
name: str
founded: int = Field(ge=1440)
location: Optional[str] = None
```
By defining the `Publisher` class, we've just created an ODMantic model 🎉. In this
example, the model will represent book publishers.
This model contains three fields:
- `name`: This is the name of the Publisher. This is a simple string field without any
specific validation, but it will be required to build a new Publisher.
- `founded`: This is the year of foundation of the Publisher. Since the printing press was invented in 1440, it would be handy to allow only values above 1440. The
`ge` keyword argument passed to the Field is exactly doing this. The model will
require a founded value greater or equal than 1440.
- `location`: This field will contain the country code of the Publisher. Defining this
field as `Optional` with a `None` default value makes it a non required field that
will be set automatically when not specified.
The collection name has been defined by ODMantic as well. In this case it will be
`publisher`.
### Create some instances
```python
instances = [
Publisher(name="HarperCollins", founded=1989, location="US"),
Publisher(name="Hachette Livre", founded=1826, location="FR"),
Publisher(name="Lulu", founded=2002)
]
```
We defined three instances of the Publisher model. They all have a `name` property as it
was required. All the foundations years are later than 1440. The last publisher has no
location specified so by default this field is set to `None` (it will be stored as
`null` in the database).
For now, those instances only exists locally. We will persist them in a database in the
next step.
### Populate the database with your instances
> For the next steps, you'll need to start a local MongoDB server.The easiest way is
> to use docker. Simply run the next command in a terminal (closing the terminal will
> terminate the MongoDB instance and remove the container).
>
> ```shell
> docker run --rm -p 27017:27017 mongo
> ```
First, let's connect to the database using the engine. In ODMantic, every database
operation is performed using the engine object.
```python
from odmantic import AIOEngine
engine = AIOEngine()
```
By default, the `AIOEngine` (stands for AsyncIOEngine) automatically tries to connect to a
MongoDB instance running locally (on port 27017). Since we didn't provide any database name, it will use
the database named `test` by default.
The next step is to persist the instances we created before. We can perform this
operation using the `AIOEngine.save_all` method.
```python
await engine.save_all(instances)
```
Most of the engine I/O methods are asynchronous, hence the `await` keyword used here.
Once the operation is complete, we should be able to see our created documents in the
database. You can use Compass or RoboMongo if you'd like to have a graphical interface.
Another possibility is to use `mongo` CLI directly:
```shell
mongo --eval "db.publisher.find({})"
```
Output:
```js
connecting to: mongodb://127.0.0.1:27017
{
"_id": ObjectId("5f67b331514d6855bc5c54c9"),
"founded": 1989,
"location": "US",
"name": "HarperCollins"
},
{
"_id": ObjectId("5f67b331514d6855bc5c54ca"),
"founded":1826,
"location": "FR",
"name": "Hachette Livre"
},
{
"_id": ObjectId("5f67b331514d6855bc5c54cb"),
"founded": 2002,
"location": null,
"name": "Lulu"
}
```
The created instances are stored in the `test` database under the `publisher` collection.
We can see that an `_id` field has been added to each document. MongoDB need this field
to act as a primary key. Actually, this field is added by ODMantic and you can access it
under the name `id`.
```python
print(instances[0].id)
#> ObjectId("5f67b331514d6855bc5c54c9")
```
### Find instances matching a criteria
Since we now have some documents in the database, we can start building some queries.
First, let's find publishers created before the 2000s:
```python
early_publishers = await engine.find(Publisher, Publisher.founded <= 2000)
print(early_publishers)
#> [Publisher(name="HarperCollins", founded=1989, location="US),
#> Publisher(name="Hachette Livre", founded=1826, location="FR")]
```
Here, we called the `engine.find` method. The first argument we need to specify is the
Model class we want to query on (in our case `Publisher`). The second argument is the
actual query. Similarly to SQLAlchemy, you can build ODMantic queries using the regular python
operators.
When awaited, the `engine.find` method will return the list of matching instances stored
in the database.
Another possibility is to query for at most one instance. For example, if we want to
retrieve a publisher from Canada (CA):
```python
ca_publisher = await engine.find_one(Publisher, Publisher.location == "CA")
print(ca_publisher)
#> None
```
Here the result is `None` because no matching instances have been found in the database.
The `engine.find_one` method returns an instance if one exists in the database
otherwise, it will return `None`.
### Modify an instance
Finally, let's edit some instances. For example, we can set the `location` for the
publisher named `Lulu`.
First, we need to gather the instance from the database:
```python
lulu = await engine.find_one(Publisher, Publisher.name == "Lulu")
print(lulu)
#> Publisher(name="Lulu", founded=2002, location=None)
```
We still have the same instance, with no location set. We can change this field:
```python
lulu.location = "US"
print(lulu)
#> Publisher(name="Lulu", founded=2002, location="US)
```
The location has been changed locally but the last step to persist this change is to
save the document:
```python
await engine.save(lulu)
```
We can now check the database state:
```shell
mongo --eval "db.publisher.find({name: 'Lulu'})"
```
Output:
```js hl_lines="5"
connecting to: mongodb://127.0.0.1:27017
{
"_id": ObjectId("5f67b331514d6855bc5c54cb"),
"founded": 2002,
"location": "US",
"name": "Lulu"
}
```
The document have been successfully updated !
Now, what if we would like to change the foundation date with an invalid one (before 1440) ?
```python
lulu.founded = 1000
#> ValidationError: 1 validation error for Publisher
#> founded
#> ensure this value is greater than 1440
#> (type=value_error.number.not_gt; limit_value=1440)
```
This will raise an exception as it's not matching the model definition.
### Next steps
If you already have experience with Pydantic and FastAPI, the [Usage with FastAPI](https://art049.github.io/odmantic/usage_fastapi/) example sould be interesting for you to get kickstarted.
Otherwise, to get started on more advanced practices like relations and building more
advanced queries, you can directly check the other sections of the
[documentation](https://art049.github.io/odmantic/).
If you wish to contribute to the project (Thank you! :smiley:), you can have a look to the
[Contributing](https://art049.github.io/odmantic/contributing/) section of the
documentation.
## License
This project is licensed under the terms of the ISC license.
art049-odmantic-07c27e3/SECURITY.md 0000664 0000000 0000000 00000001371 15135204315 0016457 0 ustar 00root root 0000000 0000000 # Security Policy
## Supported Versions
The latest production release of ODMantic is supported.
## Reporting a Vulnerability
If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: _arthur[dot]pastel[at]gmail[dot]com_
Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue.
I will try to answer as soon as possible and will keep you informed about the progress of the resolution.
## Responsible Disclosure
Please do not disclose the vulnerability publicly until it has been fixed and published.
I will try to fix the vulnerability as soon as possible and will keep you informed about the progress of the resolution.
art049-odmantic-07c27e3/Taskfile.yml 0000664 0000000 0000000 00000004620 15135204315 0017153 0 ustar 00root root 0000000 0000000 # https://taskfile.dev
version: "3"
silent: true
includes:
release: ./.github
mongodb:
taskfile: ./mongodb-cluster/Taskfile.yml
dir: ./mongodb-cluster
optional: true
tasks:
full-test:
desc: Run the tests against all supported versions.
deps:
- task: "mongodb:check"
cmds:
- tox --parallel auto
test:
desc: |
Run the tests with the current version.
deps:
- task: "mongodb:check"
cmds:
- uv run python -m pytest -rs -n auto
bench:
desc: |
Run the benches with the current version.
deps:
- task: "mongodb:check"
cmds:
- uv run python -m pytest --benchmark-enable --benchmark-only
default:
desc: |
Run the tests related to changes with the current version.
deps:
- task: mongodb
cmds:
- uv run python -m pytest -rs --testmon
coverage:
desc: Get the test coverage (xml and html) with the current version.
deps:
- task: "mongodb:check"
cmds:
- coverage run -m pytest -rs
- coverage report -m
- coverage xml
- 'echo "Generated XML report: ./coverage.xml"'
- coverage html
- 'echo "Generated HTML report: ./htmlcov/index.html"'
docs:
desc: Start the local documentation server.
cmds:
- mkdocs serve -f ./mkdocs.yml
lint:
desc: Run the linting checks.
cmds:
- pre-commit run --all-files
format:
desc: Format the code (and imports).
cmds:
- uv run python -m ruff check odmantic tests
setup:
desc: Configure the development environment.
cmds:
- task: setup:git-lfs
- task: setup:git-submodules
- task: setup:pre-commit-hook
- task: setup:deps-setup
setup:git-lfs:
cmds:
- git lfs install
- git lfs pull
status:
- test -d .git/lfs/
setup:git-submodules:
cmds:
- git submodule update --init
status:
- test -f .mongodb-cluster-action/README.md
setup:pre-commit-hook:
cmds:
- pre-commit install
status:
- test -f .git/hooks/pre-commit
setup:deps-setup:
deps:
- task: setup:uv
cmds:
- uv sync --all-extras --dev
sources:
- pyproject.toml
setup:uv:
cmds:
- pip install uv
status:
- which uv
clean:
cmds:
- rm -rf dist/
- rm -rf htmlcov/ ./.coverage ./coverage.xml
- rm -rf .task/ ./__release_notes__.md ./__version__.txt
art049-odmantic-07c27e3/dependabot.yml 0000664 0000000 0000000 00000000150 15135204315 0017510 0 ustar 00root root 0000000 0000000 version: 2
groups:
dev-dependencies:
dependency-type: development
applies-to: version-updates
art049-odmantic-07c27e3/docs/ 0000775 0000000 0000000 00000000000 15135204315 0015614 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0017721 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/api_reference/ 0000775 0000000 0000000 00000000000 15135204315 0020403 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/api_reference/bson.md 0000664 0000000 0000000 00000001644 15135204315 0021673 0 ustar 00root root 0000000 0000000 This module provides helpers to build Pydantic Models containing BSON objects.
## Pydantic model helpers
::: odmantic.bson.BaseBSONModel
selection:
members:
-
::: odmantic.bson.BSON_TYPES_ENCODERS
Encoders required to encode BSON fields (can be used in the Pydantic Model's `Config.json_encoders` parameter). See [pydantic: JSON Encoders](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.json_encoders){:target=blank_} for more details.
## Custom BSON serializer annotation
::: odmantic.bson.WithBsonSerializer
## Pydantic type helpers
Those helpers inherit directly from their respective `bson` types. They add the field
validation logic required by Pydantic to work with them. On top of this, the appropriate JSON schemas are generated for them.
::: odmantic.bson.ObjectId
::: odmantic.bson.Int64
::: odmantic.bson.Decimal128
::: odmantic.bson.Binary
::: odmantic.bson.Regex
art049-odmantic-07c27e3/docs/api_reference/config.md 0000664 0000000 0000000 00000000042 15135204315 0022166 0 ustar 00root root 0000000 0000000 ::: odmantic.config.ODMConfigDict
art049-odmantic-07c27e3/docs/api_reference/engine.md 0000664 0000000 0000000 00000000175 15135204315 0022175 0 ustar 00root root 0000000 0000000 ::: odmantic.engine.AIOEngine
::: odmantic.engine.AIOCursor
::: odmantic.engine.SyncEngine
::: odmantic.engine.SyncCursor
art049-odmantic-07c27e3/docs/api_reference/exceptions.md 0000664 0000000 0000000 00000000414 15135204315 0023105 0 ustar 00root root 0000000 0000000 ::: odmantic.exceptions.BaseEngineException
::: odmantic.exceptions.DocumentNotFoundError
::: odmantic.exceptions.DocumentParsingError
selection:
members:
-
::: odmantic.exceptions.DuplicateKeyError
selection:
members:
-
art049-odmantic-07c27e3/docs/api_reference/field.md 0000664 0000000 0000000 00000000031 15135204315 0022002 0 ustar 00root root 0000000 0000000 ::: odmantic.field.Field
art049-odmantic-07c27e3/docs/api_reference/index.md 0000664 0000000 0000000 00000000031 15135204315 0022026 0 ustar 00root root 0000000 0000000 ::: odmantic.index.Index
art049-odmantic-07c27e3/docs/api_reference/model.md 0000664 0000000 0000000 00000000525 15135204315 0022027 0 ustar 00root root 0000000 0000000 ::: odmantic.model._BaseODMModel
selection:
members:
- model_validate_doc
- model_dump_doc
- model_update
- model_copy
- model_dump
::: odmantic.model.Model
selection:
members:
-
::: odmantic.model.EmbeddedModel
selection:
members:
-
art049-odmantic-07c27e3/docs/api_reference/query.md 0000664 0000000 0000000 00000000720 15135204315 0022071 0 ustar 00root root 0000000 0000000 ::: odmantic.query.QueryExpression
## Logical Operators
::: odmantic.query.and_
::: odmantic.query.or_
::: odmantic.query.nor_
## Comparison Operators
::: odmantic.query.eq
::: odmantic.query.ne
::: odmantic.query.gt
::: odmantic.query.gte
::: odmantic.query.lt
::: odmantic.query.lte
::: odmantic.query.in_
::: odmantic.query.not_in
::: odmantic.query.match
## Sort helpers
::: odmantic.query.SortExpression
::: odmantic.query.asc
::: odmantic.query.desc
art049-odmantic-07c27e3/docs/api_reference/reference.md 0000664 0000000 0000000 00000000041 15135204315 0022656 0 ustar 00root root 0000000 0000000 ::: odmantic.reference.Reference
art049-odmantic-07c27e3/docs/api_reference/session.md 0000664 0000000 0000000 00000000325 15135204315 0022410 0 ustar 00root root 0000000 0000000
::: odmantic.session.AIOSession
::: odmantic.engine.AIOTransaction
::: odmantic.session.AIOSessionBase
::: odmantic.engine.SyncSession
::: odmantic.engine.SyncTransaction
::: odmantic.engine.SyncSessionBase
art049-odmantic-07c27e3/docs/api_reference/templates/ 0000775 0000000 0000000 00000000000 15135204315 0022401 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/api_reference/templates/python/ 0000775 0000000 0000000 00000000000 15135204315 0023722 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/api_reference/templates/python/material/ 0000775 0000000 0000000 00000000000 15135204315 0025520 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/api_reference/templates/python/material/properties.html 0000664 0000000 0000000 00000000430 15135204315 0030577 0 ustar 00root root 0000000 0000000 {% if properties %}
{% for property in properties %} {% if property != "pydantic-model" %}
{{ property }}
{% endif %} {% endfor %}
{% endif %}
art049-odmantic-07c27e3/docs/changelog.md 0000777 0000000 0000000 00000000000 15135204315 0022104 2../CHANGELOG.md ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/contributing.md 0000664 0000000 0000000 00000012540 15135204315 0020647 0 ustar 00root root 0000000 0000000 # Contributing
## Sharing feedback
This project is still quite new and therefore having your feedback will really help to
prioritize relevant feature developments :rocket:.
The easiest way to share feedback and discuss about the project is to join the [Gitter
chatroom](https://gitter.im/odmantic/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link){:target=blank_}.
If you want to contribute (thanks a lot ! :smiley:), you can open an
[issue](https://github.com/art049/odmantic/issues/new){:target=blank_} on Github.
Before creating a non obvious (typo, documentation fix) Pull Request, please make sure
to open an issue.
## Developing locally
### With the VSCode's [devcontainer](https://code.visualstudio.com/docs/remote/containers){:target=blank_} feature
This feature will make the tools/environment installation very simple as you will develop
in a container that has already been configured to run this project.
Here are the steps:
1. Clone the repository and open it with [Visual Studio
Code](https://code.visualstudio.com/){:target=blank_}.
2. Make sure that the [Remote -
Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers){:target=blank_}
(`ms-vscode-remote.remote-containers`) extension is installed.
3. Run the `Remote-Container: Reopen in Container` command (press `Ctrl`+`Shift`+`P` and
then type the command).
4. After the setup script completes, the environment is ready. You can start the local
development :fire:.
You can go to the [development tasks](#running-development-tasks) section to see the
available `task` commands.
!!! note "MongoDB container"
In this containerized development environment, a MongoDB instance should already be
running as a part of the development `docker-compose.yml` file internally used by
VSCode.
### Regular environment setup
#### Installing the tools
- [Git LFS](https://git-lfs.github.com/){:target=blank_}: used to store documentation assets in the repository
- [Docker](https://docs.docker.com/get-docker/){:target=blank_}: used to run a local MongoDB instance
- [Docker Compose](https://docs.docker.com/compose/install/){:target=blank_} (Optional): used to run a local MongoDB cluster (replica set or shards)
- [Task](https://taskfile.dev){:target=blank_}: task manager
!!! tip "Installing python based development tools"
In order to install the devtools written in python, it's recommended to use [pipx](https://pipxproject.github.io/pipx/){:target=blank_}.
```shell
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```
- [flit](https://flit.pypa.io/en/latest/){:target=blank_}: packaging system and dependency
manager
```shell
pipx install flit
```
- [tox](https://tox.readthedocs.io/en/latest/){:target=blank_}: multi-environment test runner
```shell
pipx install tox
```
- [pre-commit](https://pre-commit.com/){:target=blank_}: pre commit hook manager
```shell
pipx install pre-commit
```
!!! tip "Python versions"
If you want to test the project with multiple python versions, you'll need to
install them manually.
You can use [pyenv](https://github.com/pyenv/pyenv){:target=blank_} to
install them easily.
```shell
# Install the versions
pyenv install "3.7.9"
pyenv install "3.8.9"
pyenv install "3.9.0"
# Make the versions available locally in the project
pyenv local 3.8.6 3.7.9 3.9.0
```
#### Configuring the local environment
```shell
task setup
```
### Running development tasks
The following tasks are available for the project:
* `task setup`: Configure the development environment.
* `task lint`: Run the linting checks.
* `task format`: Format the code (and imports).
* `mongodb:standalone-docker`: Start a standalone MongoDB instance using a docker container
* `mongodb:standalone-docker:down`: Stop the standalone instance
* `mongodb:replica-compose`: Start a replica set MongoDB cluster using docker-compose
* `mongodb:replica-compose:down`: Stop the replica set cluster
* `mongodb:sharded-compose`: Start a sharded MongoDB cluster using docker-compose
* `mongodb:sharded-compose:down`: Stop the sharded MongoDB cluster
* `task test`: Run the tests with the current version.
* `task full-test`: Run the tests against all supported versions.
* `task coverage`: Get the test coverage (xml and html) with the current version.
* `task docs`: Start the local documentation server.
art049-odmantic-07c27e3/docs/css/ 0000775 0000000 0000000 00000000000 15135204315 0016404 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/css/extra.css 0000664 0000000 0000000 00000000052 15135204315 0020236 0 ustar 00root root 0000000 0000000 code {
--md-code-fg-color: #4cae4fbb;
}
art049-odmantic-07c27e3/docs/engine.md 0000664 0000000 0000000 00000030222 15135204315 0017402 0 ustar 00root root 0000000 0000000 # Engine
This engine documentation present how to work with both the Sync ([SyncEngine][odmantic.engine.SyncEngine]) and the Async ([AIOEngine][odmantic.engine.AIOEngine]) engines. The methods available for both are very close but the main difference is that the Async engine exposes coroutines instead of functions for the Sync engine.
## Creating the engine
In the previous examples, we created the engine using default parameters:
- MongoDB: running on `localhost` port `27017`
- Database name: `test`
It's possible to provide a custom client ([AsyncIOMotorClient](https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_client.html){:target=blank_} or [PyMongoClient](https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html){:target=blank_}) to the engine constructor. In the same way, the database name can be changed using the `database` keyword argument.
{{ async_sync_snippet("engine", "engine_creation.py") }}
For additional information about the MongoDB connection strings, see [this
section](https://docs.mongodb.com/manual/reference/connection-string/){:target=blank_}
of the MongoDB documentation.
!!! tip "Usage with DNS SRV records"
If you decide to use the [DNS Seed List Connection
Format](https://docs.mongodb.com/manual/reference/connection-string/#dns-seed-list-connection-format){:target=blank}
(i.e `mongodb+srv://...`), you will need to install the
[dnspython](https://pypi.org/project/dnspython/){:target=blank_} package.
## Create
There are two ways of persisting instances to the database (i.e creating new documents):
- `engine.save`: to save a single instance
- `engine.save_all`: to save multiple instances at
once
{{ async_sync_snippet("engine", "create.py", hl_lines="12 19") }}
??? abstract "Resulting documents in the `player` collection"
```json
{
"_id": ObjectId("5f85f36d6dfecacc68428a46"),
"game": "World of Warcraft",
"name": "Leeroy Jenkins"
}
{
"_id": ObjectId("5f85f36d6dfecacc68428a47"),
"game": "Counter-Strike",
"name": "Shroud"
}
{
"_id": ObjectId("5f85f36d6dfecacc68428a49"),
"game": "Starcraft",
"name": "TLO"
}
{
"_id": ObjectId("5f85f36d6dfecacc68428a48"),
"game": "Starcraft",
"name": "Serral"
}
```
!!! tip "Referenced instances"
When calling `engine.save` or
`engine.save_all`, the referenced models will are persisted
as well.
!!! warning "Upsert behavior"
The `save` and `save_all` methods behave as upsert operations ([more
details](engine.md#update)). Hence, you might overwrite documents if you save
instances with an existing primary key already existing in the database.
## Read
!!! note "Examples database content"
The next examples will consider that you have a `player` collection populated with
the documents previously created.
### Fetch a single instance
As with regular MongoDB driver, you can use the
`engine.find_one` method to get at most one
instance of a specific Model. This method will either return an instance matching the
specified criteriums or `None` if no instances have been found.
{{ async_sync_snippet("engine", "fetch_find_one.py", hl_lines="11 15-17") }}
!!! info "Missing values in documents"
While parsing the MongoDB documents into Model instances, ODMantic will use the
provided default values to populate the missing fields.
See [this section](raw_query_usage.md#advanced-parsing-behavior) for more details about document parsing.
!!! tip "Fetch using `sort`"
We can use the `sort` parameter to fetch the `Player` instance with
the first `name` in ascending order:
```python
await engine.find_one(Player, sort=Player.name)
```
Find out more on `sort` in [the dedicated section](querying.md#sorting).
### Fetch multiple instances
To get more than one instance from the database at once, you can use the
`engine.find` method.
This method will return a cursor: an [AIOCursor][odmantic.engine.AIOCursor] object for the [AIOEngine][odmantic.engine.AIOEngine] and a [SyncCursor][odmantic.engine.SyncCursor] object for the [SyncEngine][odmantic.engine.SyncEngine].
Those cursors can mainly be used in two different ways:
#### Usage as an iterator
{{ async_sync_snippet("engine", "fetch_async_for.py", hl_lines="11") }}
!!! tip "Ordering instances"
The `sort` parameter allows to order the query in ascending or descending order on
a single or multiple fields.
```python
engine.find(Player, sort=(Player.name, Player.game.desc()))
```
Find out more on `sort` in [the dedicated section](querying.md#sorting).
#### Usage as an awaitable/list
Even if the iterator usage should be preferred, in some cases it might be required
to gather all the documents from the database before processing them.
{{ async_sync_snippet("engine", "fetch_await.py", hl_lines="11") }}
!!! note "Pagination"
When using [AIOEngine.find][odmantic.engine.AIOEngine.find] or [SyncEngine.find][odmantic.engine.SyncEngine.find]
you can as well use the `skip` and `limit` keyword arguments , respectively to skip
a specified number of instances and to limit the number of fetched instances.
!!! tip "Referenced instances"
When calling `engine.find` or `engine.find_one`, the referenced models will
be recursively resolved as well by design.
!!! info "Passing the model class to `find` and `find_one`"
When using the method to retrieve instances from the database, you have to specify
the Model you want to query on as the first positional parameter. Internally, this
enables ODMantic to properly type the results.
### Count instances
You can count instances in the database by using the `engine.count` method and as with
other read methods, it's still possible to use this method with filtering queries.
{{ async_sync_snippet("engine", "count.py", hl_lines="11 14 17") }}
!!! tip "Combining multiple queries in read operations"
While using [find][odmantic.engine.AIOEngine.find],
[find_one][odmantic.engine.AIOEngine.find_one] or
[count][odmantic.engine.AIOEngine.count], you may pass as many queries as you want
as positional arguments. Those will be implicitly combined as single
[and_][odmantic.query.and_] query.
## Update
Updating an instance in the database can be done by modifying the instance locally and
saving it again to the database.
The `engine.save` and `engine.save_all` methods are actually behaving as
`upsert` operations. In other words, if the instance already exists it will be updated.
Otherwise, the related document will be created in the database.
### Modifying one field
Modifying a single field can be achieved by directly changing the instance attribute and
saving the instance.
{{ async_sync_snippet("engine", "update.py", hl_lines="13-14") }}
???+abstract "Resulting documents in the `player` collection"
```json hl_lines="6-10"
{
"_id": ObjectId("5f85f36d6dfecacc68428a46"),
"game": "World of Warcraft",
"name": "Leeroy Jenkins"
}
{
"_id": ObjectId("5f85f36d6dfecacc68428a47"),
"game": "Valorant",
"name": "Shroud"
}
{
"_id": ObjectId("5f85f36d6dfecacc68428a49"),
"game": "Starcraft",
"name": "TLO"
}
{
"_id": ObjectId("5f85f36d6dfecacc68428a48"),
"game": "Starcraft",
"name": "Serral"
}
```
### Patching multiple fields at once
The easiest way to change multiple fields at once is to use the
[Model.model_update][odmantic.model._BaseODMModel.model_update] method. This method will take either a
Pydantic object or a dictionary and update the matching fields of the instance.
#### From a Pydantic Model
{{ async_sync_snippet("engine", "patch_multiple_fields_pydantic.py", hl_lines="19-21 25 27 30 33") }}
#### From a dictionary
{{ async_sync_snippet("engine", "patch_multiple_fields_dict.py", hl_lines="16 18 21 24") }}
!!! abstract "Resulting document associated to the player"
```json hl_lines="3 4"
{
"_id": ObjectId("5f85f36d6dfecacc68428a49"),
"game": "Starcraft II",
"name": "TheLittleOne"
}
```
### Changing the primary field
Directly changing the primary field value as explained above is not
possible and a `NotImplementedError` exception will be raised if you try to do so.
The easiest way to change an instance primary field is to perform a local copy of the
instance using the [Model.copy][odmantic.model._BaseODMModel.model_copy] method.
{{ async_sync_snippet("engine", "primary_key_update.py", hl_lines="18 20 22") }}
!!! abstract "Resulting document associated to the player"
```json hl_lines="2"
{
"_id": ObjectId("ffffffffffffffffffffffff"),
"game": "Valorant",
"name": "Shroud"
}
```
!!! danger "Update data used with the copy"
The data updated by the copy method is not validated: you should **absolutely**
trust this data.
## Delete
### Delete a single instance
You can delete instance by passing them to the `engine.delete` method.
{{ async_sync_snippet("engine", "delete.py", hl_lines="14") }}
### Remove
You can delete instances that match a filter by using the
`engine.remove` method.
{{ async_sync_snippet("engine", "remove.py", hl_lines="11") }}
#### Just one
You can limit `engine.remove` to removing only one
instance by passing `just_one`.
{{ async_sync_snippet("engine", "remove_just_one.py", hl_lines="12") }}
## Consistency
### Using a Session
!!! Tip "Why are sessions needed ?"
A session is a way to
guarantee that the data you read is consistent with the data you write.
This is especially useful when you need to perform multiple operations on the
same data.
See [this document](https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/#causal-consistency){:target=blank_} for more details on causal consistency.
You can create a session by using the `engine.session` method. This method will return
either a [SyncSession][odmantic.session.SyncSession] or an
[AIOSession][odmantic.session.AIOSession] object, depending on the type of engine used.
Those session objects are context manager and can be used along with the `with` or the
`async with` keywords. Once the context is entered the `session` object exposes the same
database operation methods as the related `engine` object but execute each operation in
the session context.
{{ async_sync_snippet("engine", "save_with_session.py", hl_lines="13-23") }}
!!! Tip "Directly using driver sessions"
Every single engine method also accepts a `session` parameter. You can use this
parameter to provide an existing driver (motor or PyMongo) session that you created
manually.
!!! Tip "Accessing the underlying driver session object"
The `session.get_driver_session` method exposes the underlying driver session. This
is useful if you want to use the driver session directly to perform raw operations.
### Using a Transaction
!!! Tip "Why are transactions needed ?"
A transaction is a mechanism that allows you to execute multiple operations in a
single atomic operation. This is useful when you want to ensure that a set of
operations is atomicly performed on a specific document.
!!! Error "MongoDB transaction support"
Transactions are only supported in a replica sets (Mongo 4.0+) or sharded clusters
with replication enabled (Mongo 4.2+), if you use them in a standalone MongoDB
instance an error will be raised.
You can create a transaction directly from the engine by using the `engine.transaction`
method. This methods will either return a
[SyncTransaction][odmantic.session.SyncTransaction] or an
[AIOTransaction][odmantic.session.AIOTransaction] object. As for sessions, transaction
objects exposes the same database operation methods as the related `engine` object but
execute each operation in a transactional context.
In order to terminate a transaction you must either call the `commit` method to persist
all the changes or call the `abort` method to drop the changes introduced in the
transaction.
{{ async_sync_snippet("engine", "save_with_transaction.py", hl_lines="11-13 18-21") }}
It is also possible to create a transaction within an existing session by using
the `session.transaction` method:
{{ async_sync_snippet("engine", "transaction_from_session.py", hl_lines="11-19") }}
art049-odmantic-07c27e3/docs/examples_src/ 0000775 0000000 0000000 00000000000 15135204315 0020301 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/__init__.py 0000664 0000000 0000000 00000000000 15135204315 0022400 0 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/engine/ 0000775 0000000 0000000 00000000000 15135204315 0021546 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/engine/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0023653 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/engine/async/ 0000775 0000000 0000000 00000000000 15135204315 0022663 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/engine/async/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0024770 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/engine/async/count.py 0000664 0000000 0000000 00000000562 15135204315 0024370 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
player_count = await engine.count(Player)
print(player_count)
#> 4
cs_count = await engine.count(Player, Player.game == "Counter-Strike")
print(cs_count)
#> 1
valorant_count = await engine.count(Player, Player.game == "Valorant")
print(valorant_count)
#> 0
art049-odmantic-07c27e3/docs/examples_src/engine/async/create.py 0000664 0000000 0000000 00000000603 15135204315 0024477 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
leeroy = Player(name="Leeroy Jenkins", game="World of Warcraft")
await engine.save(leeroy)
players = [
Player(name="Shroud", game="Counter-Strike"),
Player(name="Serral", game="Starcraft"),
Player(name="TLO", game="Starcraft"),
]
await engine.save_all(players)
art049-odmantic-07c27e3/docs/examples_src/engine/async/delete.py 0000664 0000000 0000000 00000000315 15135204315 0024476 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
players = await engine.find(Player)
for player in players:
await engine.delete(player)
art049-odmantic-07c27e3/docs/examples_src/engine/async/engine_creation.py 0000664 0000000 0000000 00000000307 15135204315 0026366 0 ustar 00root root 0000000 0000000 from motor.motor_asyncio import AsyncIOMotorClient
from odmantic import AIOEngine
client = AsyncIOMotorClient("mongodb://localhost:27017/")
engine = AIOEngine(client=client, database="example_db")
art049-odmantic-07c27e3/docs/examples_src/engine/async/fetch_async_for.py 0000664 0000000 0000000 00000000506 15135204315 0026372 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
async for player in engine.find(Player, Player.game == "Starcraft"):
print(repr(player))
#> Player(id=ObjectId(...), name='TLO', game='Starcraft')
#> Player(id=ObjectId(...), name='Serral', game='Starcraft')
art049-odmantic-07c27e3/docs/examples_src/engine/async/fetch_await.py 0000664 0000000 0000000 00000000543 15135204315 0025515 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
players = await engine.find(Player, Player.game != "Starcraft")
print(players)
#> [
#> Player(id=ObjectId(...), name="Leeroy Jenkins", game="World of Warcraft"),
#> Player(id=ObjectId(...), name="Shroud", game="Counter-Strike"),
#> ]
art049-odmantic-07c27e3/docs/examples_src/engine/async/fetch_find_one.py 0000664 0000000 0000000 00000000606 15135204315 0026171 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
player = await engine.find_one(Player, Player.name == "Serral")
print(repr(player))
#> Player(id=ObjectId(...), name="Serral", game="Starcraft")
another_player = await engine.find_one(
Player, Player.name == "Player_Not_Stored_In_Database"
)
print(another_player)
#> None
art049-odmantic-07c27e3/docs/examples_src/engine/async/patch_multiple_fields_dict.py 0000664 0000000 0000000 00000001134 15135204315 0030577 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
player_tlo = await engine.find_one(Player, Player.name == "TLO")
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TLO', game='Starcraft')
# Create the patch dictionary containing the new values
patch_object = {"name": "TheLittleOne", "game": "Starcraft II"}
# Update the local instance
player_tlo.model_update(patch_object)
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TheLittleOne', game='Starcraft II')
# Finally persist the instance
await engine.save(player_tlo)
art049-odmantic-07c27e3/docs/examples_src/engine/async/patch_multiple_fields_pydantic.py 0000664 0000000 0000000 00000001417 15135204315 0031473 0 ustar 00root root 0000000 0000000 from pydantic import BaseModel
from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
player_tlo = await engine.find_one(Player, Player.name == "TLO")
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TLO', game='Starcraft')
# Create the structure of the patch object with pydantic
class PatchPlayerSchema(BaseModel):
name: str
game: str
# Create the patch object containing the new values
patch_object = PatchPlayerSchema(name="TheLittleOne", game="Starcraft II")
# Apply the patch to the instance
player_tlo.model_update(patch_object)
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TheLittleOne', game='Starcraft II')
# Finally persist again the new instance
await engine.save(player_tlo)
art049-odmantic-07c27e3/docs/examples_src/engine/async/primary_key_update.py 0000664 0000000 0000000 00000001021 15135204315 0027124 0 ustar 00root root 0000000 0000000 from bson import ObjectId
from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
shroud = await engine.find_one(Player, Player.name == "Shroud")
print(shroud.id)
#> 5f86074f6dfecacc68428a62
new_id = ObjectId("ffffffffffffffffffffffff")
# Copy the player instance with a new primary key
new_shroud = shroud.copy(update={"id": new_id})
# Delete the initial player instance
await engine.delete(shroud)
# Finally persist again the new instance
await engine.save(new_shroud)
art049-odmantic-07c27e3/docs/examples_src/engine/async/remove.py 0000664 0000000 0000000 00000000317 15135204315 0024533 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
delete_count = await engine.remove(Player, Player.game == "Warzone")
print(delete_count)
#> 2
art049-odmantic-07c27e3/docs/examples_src/engine/async/remove_just_one.py 0000664 0000000 0000000 00000000344 15135204315 0026441 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
delete_count = await engine.remove(
Player, Player.game == "Warzone", just_one=True
)
print(delete_count)
#> 1
art049-odmantic-07c27e3/docs/examples_src/engine/async/save_with_session.py 0000664 0000000 0000000 00000001002 15135204315 0026762 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
leeroy = Player(name="Leeroy Jenkins", game="World of Warcraft")
async with engine.session() as session:
await session.save_all(
[
Player(name="Shroud", game="Counter-Strike"),
Player(name="Serral", game="Starcraft"),
Player(name="TLO", game="Starcraft"),
]
)
player_count = await session.count(Player)
print(player_count)
#> 3
art049-odmantic-07c27e3/docs/examples_src/engine/async/save_with_transaction.py 0000664 0000000 0000000 00000001046 15135204315 0027634 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
async with engine.transaction() as transaction:
await transaction.save(Player(name="Leeroy Jenkins", game="WoW"))
await transaction.commit()
print(engine.count(Player))
#> 1
async with engine.transaction() as transaction:
await transaction.save(Player(name="Shroud", game="Counter-Strike"))
await transaction.save(Player(name="Serral", game="Starcraft"))
await transaction.abort()
print(engine.count(Player))
#> 1
art049-odmantic-07c27e3/docs/examples_src/engine/async/transaction_from_session.py 0000664 0000000 0000000 00000001135 15135204315 0030350 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
async with engine.session() as session:
leeroy = await session.save(Player(name="Leeroy Jenkins", game="WoW"))
shroud = await session.save(Player(name="Shroud", game="Counter-Strike"))
async with session.transaction() as transaction:
leeroy.game = "Fortnite"
await transaction.save(leeroy)
shroud.game = "Fortnite"
await transaction.save(shroud)
await transaction.commit()
print(await engine.count(Player, Player.game == "Fortnite"))
#> 2
art049-odmantic-07c27e3/docs/examples_src/engine/async/update.py 0000664 0000000 0000000 00000000410 15135204315 0024512 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Player(Model):
name: str
game: str
engine = AIOEngine()
shroud = await engine.find_one(Player, Player.name == "Shroud")
print(shroud.game)
#> Counter-Strike
shroud.game = "Valorant"
await engine.save(shroud)
art049-odmantic-07c27e3/docs/examples_src/engine/sync/ 0000775 0000000 0000000 00000000000 15135204315 0022522 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/engine/sync/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0024627 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/engine/sync/count.py 0000664 0000000 0000000 00000000542 15135204315 0024225 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
player_count = engine.count(Player)
print(player_count)
#> 4
cs_count = engine.count(Player, Player.game == "Counter-Strike")
print(cs_count)
#> 1
valorant_count = engine.count(Player, Player.game == "Valorant")
print(valorant_count)
#> 0
art049-odmantic-07c27e3/docs/examples_src/engine/sync/create.py 0000664 0000000 0000000 00000000571 15135204315 0024342 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
leeroy = Player(name="Leeroy Jenkins", game="World of Warcraft")
engine.save(leeroy)
players = [
Player(name="Shroud", game="Counter-Strike"),
Player(name="Serral", game="Starcraft"),
Player(name="TLO", game="Starcraft"),
]
engine.save_all(players)
art049-odmantic-07c27e3/docs/examples_src/engine/sync/delete.py 0000664 0000000 0000000 00000000303 15135204315 0024332 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
players = engine.find(Player)
for player in players:
engine.delete(player)
art049-odmantic-07c27e3/docs/examples_src/engine/sync/engine_creation.py 0000664 0000000 0000000 00000000257 15135204315 0026231 0 ustar 00root root 0000000 0000000 from pymongo import MongoClient
from odmantic import SyncEngine
client = MongoClient("mongodb://localhost:27017/")
engine = SyncEngine(client=client, database="example_db")
art049-odmantic-07c27e3/docs/examples_src/engine/sync/fetch_async_for.py 0000664 0000000 0000000 00000000502 15135204315 0026225 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
for player in engine.find(Player, Player.game == "Starcraft"):
print(repr(player))
#> Player(id=ObjectId(...), name='TLO', game='Starcraft')
#> Player(id=ObjectId(...), name='Serral', game='Starcraft')
art049-odmantic-07c27e3/docs/examples_src/engine/sync/fetch_await.py 0000664 0000000 0000000 00000000545 15135204315 0025356 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
players = list(engine.find(Player, Player.game != "Starcraft"))
print(players)
#> [
#> Player(id=ObjectId(...), name="Leeroy Jenkins", game="World of Warcraft"),
#> Player(id=ObjectId(...), name="Shroud", game="Counter-Strike"),
#> ]
art049-odmantic-07c27e3/docs/examples_src/engine/sync/fetch_find_one.py 0000664 0000000 0000000 00000000574 15135204315 0026034 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
player = engine.find_one(Player, Player.name == "Serral")
print(repr(player))
#> Player(id=ObjectId(...), name="Serral", game="Starcraft")
another_player = engine.find_one(
Player, Player.name == "Player_Not_Stored_In_Database"
)
print(another_player)
#> None
art049-odmantic-07c27e3/docs/examples_src/engine/sync/patch_multiple_fields_dict.py 0000664 0000000 0000000 00000001122 15135204315 0030433 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
player_tlo = engine.find_one(Player, Player.name == "TLO")
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TLO', game='Starcraft')
# Create the patch dictionary containing the new values
patch_object = {"name": "TheLittleOne", "game": "Starcraft II"}
# Update the local instance
player_tlo.model_update(patch_object)
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TheLittleOne', game='Starcraft II')
# Finally persist the instance
engine.save(player_tlo)
art049-odmantic-07c27e3/docs/examples_src/engine/sync/patch_multiple_fields_pydantic.py 0000664 0000000 0000000 00000001405 15135204315 0031327 0 ustar 00root root 0000000 0000000 from pydantic import BaseModel
from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
player_tlo = engine.find_one(Player, Player.name == "TLO")
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TLO', game='Starcraft')
# Create the structure of the patch object with pydantic
class PatchPlayerSchema(BaseModel):
name: str
game: str
# Create the patch object containing the new values
patch_object = PatchPlayerSchema(name="TheLittleOne", game="Starcraft II")
# Apply the patch to the instance
player_tlo.model_update(patch_object)
print(repr(player_tlo))
#> Player(id=ObjectId(...), name='TheLittleOne', game='Starcraft II')
# Finally persist again the new instance
engine.save(player_tlo)
art049-odmantic-07c27e3/docs/examples_src/engine/sync/primary_key_update.py 0000664 0000000 0000000 00000001001 15135204315 0026761 0 ustar 00root root 0000000 0000000 from bson import ObjectId
from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
shroud = engine.find_one(Player, Player.name == "Shroud")
print(shroud.id)
#> 5f86074f6dfecacc68428a62
new_id = ObjectId("ffffffffffffffffffffffff")
# Copy the player instance with a new primary key
new_shroud = shroud.copy(update={"id": new_id})
# Delete the initial player instance
engine.delete(shroud)
# Finally persist again the new instance
engine.save(new_shroud)
art049-odmantic-07c27e3/docs/examples_src/engine/sync/remove.py 0000664 0000000 0000000 00000000313 15135204315 0024366 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
delete_count = engine.remove(Player, Player.game == "Warzone")
print(delete_count)
#> 2
art049-odmantic-07c27e3/docs/examples_src/engine/sync/remove_just_one.py 0000664 0000000 0000000 00000000340 15135204315 0026274 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
delete_count = engine.remove(
Player, Player.game == "Warzone", just_one=True
)
print(delete_count)
#> 1
art049-odmantic-07c27e3/docs/examples_src/engine/sync/save_with_session.py 0000664 0000000 0000000 00000000762 15135204315 0026635 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
leeroy = Player(name="Leeroy Jenkins", game="World of Warcraft")
with engine.session() as session:
session.save_all(
[
Player(name="Shroud", game="Counter-Strike"),
Player(name="Serral", game="Starcraft"),
Player(name="TLO", game="Starcraft"),
]
)
player_count = session.count(Player)
print(player_count)
#> 3
art049-odmantic-07c27e3/docs/examples_src/engine/sync/save_with_transaction.py 0000664 0000000 0000000 00000000776 15135204315 0027504 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
with engine.transaction() as transaction:
transaction.save(Player(name="Leeroy Jenkins", game="WoW"))
transaction.commit()
print(engine.count(Player))
#> 1
with engine.transaction() as transaction:
transaction.save(Player(name="Shroud", game="Counter-Strike"))
transaction.save(Player(name="Serral", game="Starcraft"))
transaction.abort()
print(engine.count(Player))
#> 1
art049-odmantic-07c27e3/docs/examples_src/engine/sync/transaction_from_session.py 0000664 0000000 0000000 00000001057 15135204315 0030212 0 ustar 00root root 0000000 0000000 from odmantic import Model, SyncEngine
class Player(Model):
name: str
game: str
engine = SyncEngine()
with engine.session() as session:
leeroy = session.save(Player(name="Leeroy Jenkins", game="WoW"))
shroud = session.save(Player(name="Shroud", game="Counter-Strike"))
with session.transaction() as transaction:
leeroy.game = "Fortnite"
transaction.save(leeroy)
shroud.game = "Fortnite"
transaction.save(shroud)
transaction.commit()
print(engine.count(Player, Player.game == "Fortnite"))
#> 2
art049-odmantic-07c27e3/docs/examples_src/engine/sync/update.py 0000664 0000000 0000000 00000000376 15135204315 0024364 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class Player(Model):
name: str
game: str
engine = SyncEngine()
shroud = engine.find_one(Player, Player.name == "Shroud")
print(shroud.game)
#> Counter-Strike
shroud.game = "Valorant"
engine.save(shroud)
art049-odmantic-07c27e3/docs/examples_src/fields/ 0000775 0000000 0000000 00000000000 15135204315 0021547 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/fields/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0023654 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/fields/async/ 0000775 0000000 0000000 00000000000 15135204315 0022664 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/fields/async/custom_key_name.py 0000664 0000000 0000000 00000000255 15135204315 0026422 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Field, Model
class Player(Model):
name: str = Field(key_name="username")
engine = AIOEngine()
await engine.save(Player(name="Jack"))
art049-odmantic-07c27e3/docs/examples_src/fields/async/custom_primary_field.py 0000664 0000000 0000000 00000000374 15135204315 0027462 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Field, Model
class Player(Model):
name: str = Field(primary_field=True)
leeroy = Player(name="Leeroy Jenkins")
print(repr(leeroy))
#> Player(name="Leeroy Jenkins")
engine = AIOEngine()
await engine.save(leeroy)
art049-odmantic-07c27e3/docs/examples_src/fields/async/indexed_field.py 0000664 0000000 0000000 00000000266 15135204315 0026025 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Field, Model
class Player(Model):
name: str
score: int = Field(index=True)
engine = AIOEngine()
await engine.configure_database([Player])
art049-odmantic-07c27e3/docs/examples_src/fields/async/unique_field.py 0000664 0000000 0000000 00000000712 15135204315 0025707 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Field, Model
class Player(Model):
name: str = Field(unique=True)
engine = AIOEngine()
await engine.configure_database([Player])
leeroy = Player(name="Leeroy")
await engine.save(leeroy)
another_leeroy = Player(name="Leeroy")
await engine.save(another_leeroy)
#> Raises odmantic.exceptions.DuplicateKeyError:
#> Duplicate key error for: Player.
#> Instance: id=ObjectId('6314b4c25a19444bfe0c0be5') name='Leeroy'
art049-odmantic-07c27e3/docs/examples_src/fields/container_dict.py 0000664 0000000 0000000 00000001061 15135204315 0025104 0 ustar 00root root 0000000 0000000 from typing import Dict, Union
from odmantic import Model
class SimpleDictModel(Model):
field: dict
print(SimpleDictModel(field={18: "a string", True: 42, 18.3: [1, 2, 3]}).field)
#> {18: 'a string', True: 42, 18.3: [1, 2, 3]}
class IntStrDictModel(Model):
field: Dict[int, str]
print(IntStrDictModel(field={1: "one", 2: "two"}).field)
#> {1: 'one', 2: 'two'}
class IntBoolStrDictModel(Model):
field: Dict[int, Union[bool, str]]
print(IntBoolStrDictModel(field={0: False, 1: True, 3: "three"}).field)
#> {0: False, 1: True, 3: 'three'}
art049-odmantic-07c27e3/docs/examples_src/fields/container_list.py 0000664 0000000 0000000 00000001104 15135204315 0025132 0 ustar 00root root 0000000 0000000 from typing import List, Union
from odmantic import Model
class SimpleListModel(Model):
field: list
print(SimpleListModel(field=[1, "a", True]).field)
#> [1, 'a', True]
print(SimpleListModel(field=(1, "a", True)).field)
#> [1, 'a', True]
class IntListModel(Model):
field: List[int]
print(IntListModel(field=[1, 5]).field)
#> [1, 5]
print(IntListModel(field=(1, 5)).field)
#> [1, 5]
class IntStrListModel(Model):
field: List[Union[int, str]]
print(IntStrListModel(field=[1, "b"]).field)
#> [1, 'b']
print(IntStrListModel(field=(1, "b")).field)
#> [1, 'b']
art049-odmantic-07c27e3/docs/examples_src/fields/container_tuple.py 0000664 0000000 0000000 00000001116 15135204315 0025313 0 ustar 00root root 0000000 0000000 from typing import Tuple
from odmantic import Model
class SimpleTupleModel(Model):
field: tuple
print(SimpleTupleModel(field=[1, "a", True]).field)
#> (1, 'a', True)
print(SimpleTupleModel(field=(1, "a", True)).field)
#> (1, 'a', True)
class TwoIntTupleModel(Model):
field: Tuple[int, int]
print(SimpleTupleModel(field=(1, 10)).field)
#> (1, 10)
print(SimpleTupleModel(field=[1, 10]).field)
#> (1, 10)
class IntTupleModel(Model):
field: Tuple[int, ...]
print(IntTupleModel(field=(1,)).field)
#> (1,)
print(IntTupleModel(field=[1, 2, 3, 10]).field)
#> (1, 2, 3, 10)
art049-odmantic-07c27e3/docs/examples_src/fields/custom_bson_serialization.py 0000664 0000000 0000000 00000002070 15135204315 0027410 0 ustar 00root root 0000000 0000000 from typing import Annotated
from odmantic import AIOEngine, Model, WithBsonSerializer
class ASCIISerializedAsBinaryBase(str):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if isinstance(v, bytes): # Handle data coming from MongoDB
return v.decode("ascii")
if not isinstance(v, str):
raise TypeError("string required")
if not v.isascii():
raise ValueError("Only ascii characters are allowed")
return v
def serialize_ascii_to_bytes(v: ASCIISerializedAsBinaryBase) -> bytes:
# We can encode this string as ascii since it contains
# only ascii characters
bytes_ = v.encode("ascii")
return bytes_
ASCIISerializedAsBinary = Annotated[
ASCIISerializedAsBinaryBase, WithBsonSerializer(serialize_ascii_to_bytes)
]
class Example(Model):
field: ASCIISerializedAsBinary
engine = AIOEngine()
await engine.save(Example(field="hello world"))
fetched = await engine.find_one(Example)
print(fetched.field)
#> hello world
art049-odmantic-07c27e3/docs/examples_src/fields/custom_field_validators.py 0000664 0000000 0000000 00000002553 15135204315 0027033 0 ustar 00root root 0000000 0000000 from typing import ClassVar
from pydantic import ValidationError, validator
from odmantic import Model
class SmallRectangle(Model):
MAX_SIDE_SIZE: ClassVar[float] = 10
length: float
width: float
@validator("width", "length")
def check_small_sides(cls, v):
if v > cls.MAX_SIDE_SIZE:
raise ValueError(f"side is greater than {cls.MAX_SIDE_SIZE}")
return v
@validator("width")
def check_width_length(cls, width, values, **kwargs):
length = values.get("length")
if length is not None and width > length:
raise ValueError("width can't be greater than length")
return width
print(SmallRectangle(length=2, width=1))
#> id=ObjectId('5f81e3c073103f509f97e374'), length=2.0, width=1.0
try:
SmallRectangle(length=2)
except ValidationError as e:
print(e)
"""
1 validation error for SmallRectangle
width
field required (type=value_error.missing)
"""
try:
SmallRectangle(length=2, width=3)
except ValidationError as e:
print(e)
"""
1 validation error for SmallRectangle
width
width can't be greater than length (type=value_error)
"""
try:
SmallRectangle(length=40, width=3)
except ValidationError as e:
print(e)
"""
1 validation error for SmallRectangle
length
side is greater than 10 (type=value_error)
"""
art049-odmantic-07c27e3/docs/examples_src/fields/default_value.py 0000664 0000000 0000000 00000000305 15135204315 0024737 0 ustar 00root root 0000000 0000000 from odmantic import Model
class Player(Model):
name: str
level: int = 0
p = Player(name="Dash")
print(repr(p))
#> Player(id=ObjectId('5f7cd4be16af832772f1615e'), name='Dash', level=0)
art049-odmantic-07c27e3/docs/examples_src/fields/default_value_field.py 0000664 0000000 0000000 00000000341 15135204315 0026102 0 ustar 00root root 0000000 0000000 from odmantic import Field, Model
class Player(Model):
name: str
level: int = Field(default=1, ge=1)
p = Player(name="Dash")
print(repr(p))
#> Player(id=ObjectId('5f7cdbfbb54a94e9e8717c77'), name='Dash', level=1)
art049-odmantic-07c27e3/docs/examples_src/fields/enum.py 0000664 0000000 0000000 00000000756 15135204315 0023075 0 ustar 00root root 0000000 0000000 from enum import Enum
from odmantic import AIOEngine, Model
class TreeKind(str, Enum):
BIG = "big"
SMALL = "small"
class Tree(Model):
name: str
kind: TreeKind
sequoia = Tree(name="Sequoia", kind=TreeKind.BIG)
print(sequoia.kind)
#> TreeKind.BIG
print(sequoia.kind == "big")
#> True
spruce = Tree(name="Spruce", kind="small")
print(spruce.kind)
#> TreeKind.SMALL
print(spruce.kind == TreeKind.SMALL)
#> True
engine = AIOEngine()
await engine.save_all([sequoia, spruce])
art049-odmantic-07c27e3/docs/examples_src/fields/inconsistent_enum_1.py 0000664 0000000 0000000 00000000217 15135204315 0026105 0 ustar 00root root 0000000 0000000 from enum import Enum, auto
class Color(Enum):
RED = auto()
BLUE = auto()
print(Color.RED.value)
#> 1
print(Color.BLUE.value)
#> 2
art049-odmantic-07c27e3/docs/examples_src/fields/inconsistent_enum_2.py 0000664 0000000 0000000 00000000300 15135204315 0026077 0 ustar 00root root 0000000 0000000 from enum import Enum, auto
class Color(Enum):
RED = auto()
GREEN = auto()
BLUE = auto()
print(Color.RED.value)
#> 1
print(Color.GREEN.value)
#> 2
print(Color.BLUE.value)
#> 3
art049-odmantic-07c27e3/docs/examples_src/fields/objectid.py 0000664 0000000 0000000 00000000400 15135204315 0023676 0 ustar 00root root 0000000 0000000 from odmantic import Model
class Player(Model):
name: str
leeroy = Player(name="Leeroy Jenkins")
print(leeroy.id)
#> ObjectId('5ed50fcad11d1975aa3d7a28')
print(repr(leeroy))
#> Player(id=ObjectId('5ed50fcad11d1975aa3d7a28'), name="Leeroy Jenkins")
art049-odmantic-07c27e3/docs/examples_src/fields/optional.py 0000664 0000000 0000000 00000000257 15135204315 0023752 0 ustar 00root root 0000000 0000000 from typing import Optional
from odmantic import Model
class Person(Model):
name: str
age: Optional[int] = None
john = Person(name="John")
print(john.age)
#> None
art049-odmantic-07c27e3/docs/examples_src/fields/sync/ 0000775 0000000 0000000 00000000000 15135204315 0022523 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/fields/sync/custom_key_name.py 0000664 0000000 0000000 00000000251 15135204315 0026255 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Field, Model
class Player(Model):
name: str = Field(key_name="username")
engine = SyncEngine()
engine.save(Player(name="Jack"))
art049-odmantic-07c27e3/docs/examples_src/fields/sync/custom_primary_field.py 0000664 0000000 0000000 00000000370 15135204315 0027315 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Field, Model
class Player(Model):
name: str = Field(primary_field=True)
leeroy = Player(name="Leeroy Jenkins")
print(repr(leeroy))
#> Player(name="Leeroy Jenkins")
engine = SyncEngine()
engine.save(leeroy)
art049-odmantic-07c27e3/docs/examples_src/fields/sync/indexed_field.py 0000664 0000000 0000000 00000000262 15135204315 0025660 0 ustar 00root root 0000000 0000000 from odmantic import Field, Model, SyncEngine
class Player(Model):
name: str
score: int = Field(index=True)
engine = SyncEngine()
engine.configure_database([Player])
art049-odmantic-07c27e3/docs/examples_src/fields/sync/unique_field.py 0000664 0000000 0000000 00000000672 15135204315 0025553 0 ustar 00root root 0000000 0000000 from odmantic import Field, Model, SyncEngine
class Player(Model):
name: str = Field(unique=True)
engine = SyncEngine()
engine.configure_database([Player])
leeroy = Player(name="Leeroy")
engine.save(leeroy)
another_leeroy = Player(name="Leeroy")
engine.save(another_leeroy)
#> Raises odmantic.exceptions.DuplicateKeyError:
#> Duplicate key error for: Player.
#> Instance: id=ObjectId('6314b4c25a19444bfe0c0be5') name='Leeroy'
art049-odmantic-07c27e3/docs/examples_src/fields/union.py 0000664 0000000 0000000 00000000360 15135204315 0023250 0 ustar 00root root 0000000 0000000 from typing import Union
from odmantic import Model
class Thing(Model):
ref_id: Union[int, str]
thing_1 = Thing(ref_id=42)
print(thing_1.ref_id)
#> 42
thing_2 = Thing(ref_id="i am a string")
print(thing_2.ref_id)
#> i am a string
art049-odmantic-07c27e3/docs/examples_src/fields/validation_field_descriptor.py 0000664 0000000 0000000 00000001015 15135204315 0027651 0 ustar 00root root 0000000 0000000 from typing import List
from odmantic import Field, Model
class ExampleModel(Model):
small_int: int = Field(le=10)
big_int: int = Field(gt=1000)
even_int: int = Field(multiple_of=2)
small_float: float = Field(lt=10)
big_float: float = Field(ge=1e10)
short_string: str = Field(max_length=10)
long_string: str = Field(min_length=100)
string_starting_with_the: str = Field(regex=r"^The")
short_str_list: List[str] = Field(max_items=5)
long_str_list: List[str] = Field(min_items=15)
art049-odmantic-07c27e3/docs/examples_src/fields/validation_strict_types.py 0000664 0000000 0000000 00000000305 15135204315 0027065 0 ustar 00root root 0000000 0000000 from pydantic import StrictBool, StrictFloat, StrictStr
from odmantic import Model
class ExampleModel(Model):
strict_bool: StrictBool
strict_float: StrictFloat
strict_str: StrictStr
art049-odmantic-07c27e3/docs/examples_src/modeling/ 0000775 0000000 0000000 00000000000 15135204315 0022077 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/modeling/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0024204 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/modeling/async/ 0000775 0000000 0000000 00000000000 15135204315 0023214 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/modeling/async/index_creation.py 0000664 0000000 0000000 00000000220 15135204315 0026553 0 ustar 00root root 0000000 0000000 # ... Continuation of the previous snippet ...
from odmantic import AIOEngine
engine = AIOEngine()
await engine.configure_database([Product])
art049-odmantic-07c27e3/docs/examples_src/modeling/compound_index.py 0000664 0000000 0000000 00000000562 15135204315 0025467 0 ustar 00root root 0000000 0000000 from odmantic import Field, Index, Model
class Product(Model):
name: str = Field(index=True)
stock: int
category: str
sku: str = Field(unique=True)
model_config = {
"indexes": lambda: [
Index(Product.name, Product.stock, name="name_stock_index"),
Index(Product.name, Product.category, unique=True),
]
}
art049-odmantic-07c27e3/docs/examples_src/modeling/compound_index_sort_order.py 0000664 0000000 0000000 00000000371 15135204315 0027727 0 ustar 00root root 0000000 0000000 from datetime import datetime
from odmantic import Index, Model
from odmantic.query import asc, desc
class Event(Model):
username: str
date: datetime
model_config = {"indexes": lambda: [Index(asc(Event.username), desc(Event.date))]}
art049-odmantic-07c27e3/docs/examples_src/modeling/custom_text_index.py 0000664 0000000 0000000 00000000436 15135204315 0026221 0 ustar 00root root 0000000 0000000 import pymongo
from odmantic import Model
class Post(Model):
title: str
content: str
model_config = {
"indexes": lambda: [
pymongo.IndexModel(
[(+Post.title, pymongo.TEXT), (+Post.content, pymongo.TEXT)]
)
]
}
art049-odmantic-07c27e3/docs/examples_src/modeling/custom_validators.py 0000664 0000000 0000000 00000002675 15135204315 0026225 0 ustar 00root root 0000000 0000000 from typing import ClassVar
from pydantic import ValidationError, model_validator
from odmantic import Model
class SmallRectangle(Model):
MAX_AREA: ClassVar[float] = 9
length: float
width: float
@model_validator(mode="before")
def check_width_length(cls, values):
length = values.get("length", 0)
width = values.get("width", 0)
if width > length:
raise ValueError("width can't be greater than length")
return values
@model_validator(mode="before")
def check_area(cls, values):
length = values.get("length", 0)
width = values.get("width", 0)
if length * width > cls.MAX_AREA:
raise ValueError(f"area is greater than {cls.MAX_AREA}")
return values
print(SmallRectangle(length=2, width=1))
# > id=ObjectId('5f81e3c073103f509f97e374'), length=2.0, width=1.0
try:
SmallRectangle(length=2)
except ValidationError as e:
print(e)
"""
1 validation error for SmallRectangle
width
field required (type=value_error.missing)
"""
try:
SmallRectangle(length=2, width=3)
except ValidationError as e:
print(e)
"""
1 validation error for SmallRectangle
Value error, width can't be greater than length
"""
try:
SmallRectangle(length=4, width=3)
except ValidationError as e:
print(e)
"""
1 validation error for SmallRectangle
__root__
Value error, area is greater than 9
"""
art049-odmantic-07c27e3/docs/examples_src/modeling/many_to_many.py 0000664 0000000 0000000 00000001136 15135204315 0025144 0 ustar 00root root 0000000 0000000 from typing import List
from bson import ObjectId
from odmantic import AIOEngine, Model
class Author(Model):
name: str
class Book(Model):
title: str
pages: int
author_ids: List[ObjectId]
david = Author(name="David Beazley")
brian = Author(name="Brian K. Jones")
python_cookbook = Book(
title="Python Cookbook", pages=706, author_ids=[david.id, brian.id]
)
python_essentials = Book(
title="Python Essential Reference", pages=717, author_ids=[brian.id]
)
engine = AIOEngine()
await engine.save_all((david, brian))
await engine.save_all((python_cookbook, python_essentials))
art049-odmantic-07c27e3/docs/examples_src/modeling/many_to_many_1.py 0000664 0000000 0000000 00000000472 15135204315 0025366 0 ustar 00root root 0000000 0000000 book = await engine.find_one(Book, Book.title == "Python Cookbook")
authors = await engine.find(Author, Author.id.in_(book.author_ids))
print(authors)
#> [
#> Author(id=ObjectId("5f7a37dc7311be1362e1da4e"), name="David Beazley"),
#> Author(id=ObjectId("5f7a37dc7311be1362e1da4f"), name="Brian K. Jones"),
#> ]
art049-odmantic-07c27e3/docs/examples_src/modeling/many_to_one.py 0000664 0000000 0000000 00000001172 15135204315 0024761 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model, Reference
class Publisher(Model):
name: str
founded: int
location: str
class Book(Model):
title: str
pages: int
publisher: Publisher = Reference()
hachette = Publisher(name="Hachette Livre", founded=1826, location="FR")
harper = Publisher(name="HarperCollins", founded=1989, location="US")
books = [
Book(title="They Didn't See Us Coming", pages=304, publisher=hachette),
Book(title="This Isn't Happening", pages=256, publisher=hachette),
Book(title="Prodigal Summer", pages=464, publisher=harper),
]
engine = AIOEngine()
await engine.save_all(books)
art049-odmantic-07c27e3/docs/examples_src/modeling/one_to_many.py 0000664 0000000 0000000 00000001237 15135204315 0024763 0 ustar 00root root 0000000 0000000 from typing import List
from odmantic import AIOEngine, EmbeddedModel, Model
class Address(EmbeddedModel):
street: str
city: str
state: str
zipcode: str
class Customer(Model):
name: str
addresses: List[Address]
customer = Customer(
name="John Doe",
addresses=[
Address(
street="1757 Birch Street",
city="Greenwood",
state="Indiana",
zipcode="46142",
),
Address(
street="262 Barnes Avenue",
city="Cincinnati",
state="Ohio",
zipcode="45216",
),
],
)
engine = AIOEngine()
await engine.save(customer)
art049-odmantic-07c27e3/docs/examples_src/modeling/one_to_one.py 0000664 0000000 0000000 00000001101 15135204315 0024566 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, EmbeddedModel, Model
class CapitalCity(EmbeddedModel):
name: str
population: int
class Country(Model):
name: str
currency: str
capital_city: CapitalCity
countries = [
Country(
name="Switzerland",
currency="Swiss franc",
capital_city=CapitalCity(name="Bern", population=1035000),
),
Country(
name="Sweden",
currency="Swedish krona",
capital_city=CapitalCity(name="Stockholm", population=975904),
),
]
engine = AIOEngine()
await engine.save_all(countries)
art049-odmantic-07c27e3/docs/examples_src/modeling/one_to_one_1.py 0000664 0000000 0000000 00000000415 15135204315 0025015 0 ustar 00root root 0000000 0000000 await engine.find_one(
Country, Country.capital_city.name == "Stockholm"
)
#> Country(
#> id=ObjectId("5f79d7e8b305f24ca43593e2"),
#> name="Sweden",
#> currency="Swedish krona",
#> capital_city=CapitalCity(name="Stockholm", population=975904),
#> )
art049-odmantic-07c27e3/docs/examples_src/modeling/sync/ 0000775 0000000 0000000 00000000000 15135204315 0023053 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/modeling/sync/index_creation.py 0000664 0000000 0000000 00000000214 15135204315 0026415 0 ustar 00root root 0000000 0000000 # ... Continuation of the previous snippet ...
from odmantic import SyncEngine
engine = SyncEngine()
engine.configure_database([Product])
art049-odmantic-07c27e3/docs/examples_src/querying/ 0000775 0000000 0000000 00000000000 15135204315 0022144 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/querying/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0024251 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/querying/and.py 0000664 0000000 0000000 00000000614 15135204315 0023261 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
size: float
(Tree.name == "Spruce") & (Tree.size <= 2)
#> QueryExpression(
#> {
#> "$and": (
#> QueryExpression({"name": {"$eq": "Spruce"}}),
#> QueryExpression({"size": {"$lte": 2}}),
#> )
#> }
#> )
query.and_(Tree.name == "Spruce", Tree.size <= 2)
#> ... same output ...
art049-odmantic-07c27e3/docs/examples_src/querying/asc.py 0000664 0000000 0000000 00000000576 15135204315 0023274 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model, query
engine = AIOEngine()
class Tree(Model):
name: str
average_size: float
# The following queries are equivalent,
# they will sort `Tree` by ascending `average_size`
await engine.find(Tree, sort=Tree.average_size)
await engine.find(Tree, sort=Tree.average_size.asc())
await engine.find(Tree, sort=query.asc(Tree.average_size))
art049-odmantic-07c27e3/docs/examples_src/querying/desc.py 0000664 0000000 0000000 00000000521 15135204315 0023432 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model, query
engine = AIOEngine()
class Tree(Model):
name: str
average_size: float
# The following queries are equivalent,
# they will sort `Tree` by descending `average_size`
await engine.find(Tree, sort=Tree.average_size.desc())
await engine.find(Tree, sort=query.desc(Tree.average_size))
art049-odmantic-07c27e3/docs/examples_src/querying/embedded.py 0000664 0000000 0000000 00000000640 15135204315 0024247 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, EmbeddedModel, Model
class CapitalCity(EmbeddedModel):
name: str
population: int
class Country(Model):
name: str
currency: str
capital_city: CapitalCity
Country.capital_city.name == "Paris"
#> QueryExpression({'capital_city.name': {'$eq': 'Paris'}})
Country.capital_city.population > 10 ** 6
#> QueryExpression({'capital_city.population': {'$gt': 1000000}})
art049-odmantic-07c27e3/docs/examples_src/querying/embedded_sort.py 0000664 0000000 0000000 00000000517 15135204315 0025321 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, EmbeddedModel, Model
from odmantic.query import desc
class CapitalCity(EmbeddedModel):
name: str
population: int
class Country(Model):
name: str
currency: str
capital_city: CapitalCity
engine = AIOEngine()
await engine.find(Country, sort=desc(Country.capital_city.population))
art049-odmantic-07c27e3/docs/examples_src/querying/enum.py 0000664 0000000 0000000 00000000664 15135204315 0023470 0 ustar 00root root 0000000 0000000 from enum import Enum
from odmantic import Model, query
class TreeKind(str, Enum):
BIG = "big"
SMALL = "small"
class Tree(Model):
name: str
average_size: float
kind: TreeKind
Tree.kind == TreeKind.SMALL
#> QueryExpression({'kind': {'$eq': 'small'}})
Tree.kind.eq(TreeKind.SMALL)
#> QueryExpression({'kind': {'$eq': 'small'}})
query.eq(Tree.kind, TreeKind.SMALL)
#> QueryExpression({'kind': {'$eq': 'small'}})
art049-odmantic-07c27e3/docs/examples_src/querying/equal.py 0000664 0000000 0000000 00000000472 15135204315 0023630 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
average_size: float
Tree.name == "Spruce"
#> QueryExpression({'name': {'$eq': 'Spruce'}})
Tree.name.eq("Spruce")
#> QueryExpression({'name': {'$eq': 'Spruce'}})
query.eq(Tree.name, "Spruce")
#> QueryExpression({'name': {'$eq': 'Spruce'}})
art049-odmantic-07c27e3/docs/examples_src/querying/gt_e.py 0000664 0000000 0000000 00000001046 15135204315 0023435 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
average_size: float
Tree.average_size > 2
#> QueryExpression({'average_size': {'$gt': 2}})
Tree.average_size.gt(2)
#> QueryExpression({'average_size': {'$gt': 2}})
query.gt(Tree.average_size, 2)
#> QueryExpression({'average_size': {'$gt': 2}})
Tree.average_size >= 2
#> QueryExpression({'average_size': {'$gte': 2}})
Tree.average_size.gte(2)
#> QueryExpression({'average_size': {'$gte': 2}})
query.gte(Tree.average_size, 2)
#> QueryExpression({'average_size': {'$gte': 2}})
art049-odmantic-07c27e3/docs/examples_src/querying/in.py 0000664 0000000 0000000 00000000436 15135204315 0023127 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
average_size: float
Tree.name.in_(["Spruce", "Pine"])
#> QueryExpression({'name': {'$in': ['Spruce', 'Pine']}})
query.in_(Tree.name, ["Spruce", "Pine"])
#> QueryExpression({'name': {'$in': ['Spruce', 'Pine']}})
art049-odmantic-07c27e3/docs/examples_src/querying/lt_e.py 0000664 0000000 0000000 00000001046 15135204315 0023442 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
average_size: float
Tree.average_size < 2
#> QueryExpression({'average_size': {'$lt': 2}})
Tree.average_size.lt(2)
#> QueryExpression({'average_size': {'$lt': 2}})
query.lt(Tree.average_size, 2)
#> QueryExpression({'average_size': {'$lt': 2}})
Tree.average_size <= 2
#> QueryExpression({'average_size': {'$lte': 2}})
Tree.average_size.lte(2)
#> QueryExpression({'average_size': {'$lte': 2}})
query.lte(Tree.average_size, 2)
#> QueryExpression({'average_size': {'$lte': 2}})
art049-odmantic-07c27e3/docs/examples_src/querying/match.py 0000664 0000000 0000000 00000000356 15135204315 0023616 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
Tree.name.match(r"^Spruce")
#> QueryExpression({'name': re.compile('^Spruce')})
query.match(Tree.name, r"^Spruce")
#> QueryExpression({'name': re.compile('^Spruce')})
art049-odmantic-07c27e3/docs/examples_src/querying/multiple_sort.py 0000664 0000000 0000000 00000000473 15135204315 0025424 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model, query
engine = AIOEngine()
class Tree(Model):
name: str
average_size: float
# This query will first sort on ascending `average_size`, then
# on descending `name` when `average_size` is the same
await engine.find(Tree, sort=(Tree.average_size, Tree.name.desc()))
art049-odmantic-07c27e3/docs/examples_src/querying/nor.py 0000664 0000000 0000000 00000000510 15135204315 0023310 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
size: float
query.nor_(Tree.name == "Spruce", Tree.size > 2)
#> QueryExpression(
#> {
#> "$nor": (
#> QueryExpression({"name": {"$eq": "Spruce"}}),
#> QueryExpression({"size": {"$gt": 2}}),
#> )
#> }
#> )
art049-odmantic-07c27e3/docs/examples_src/querying/not_equal.py 0000664 0000000 0000000 00000000472 15135204315 0024510 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
average_size: float
Tree.name != "Spruce"
#> QueryExpression({'name': {'$ne': 'Spruce'}})
Tree.name.ne("Spruce")
#> QueryExpression({'name': {'$ne': 'Spruce'}})
query.ne(Tree.name, "Spruce")
#> QueryExpression({'name': {'$ne': 'Spruce'}})
art049-odmantic-07c27e3/docs/examples_src/querying/not_in.py 0000664 0000000 0000000 00000000446 15135204315 0024010 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
average_size: float
Tree.name.not_in(["Spruce", "Pine"])
#> QueryExpression({'name': {'$nin': ['Spruce', 'Pine']}})
query.not_in(Tree.name, ["Spruce", "Pine"])
#> QueryExpression({'name': {'$nin': ['Spruce', 'Pine']}})
art049-odmantic-07c27e3/docs/examples_src/querying/or.py 0000664 0000000 0000000 00000000607 15135204315 0023141 0 ustar 00root root 0000000 0000000 from odmantic import Model, query
class Tree(Model):
name: str
size: float
(Tree.name == "Spruce") | (Tree.size > 2)
#> QueryExpression(
#> {
#> "$or": (
#> QueryExpression({"name": {"$eq": "Spruce"}}),
#> QueryExpression({"size": {"$gt": 2}}),
#> )
#> }
#> )
query.or_(Tree.name == "Spruce", Tree.size > 2)
#> ... same output ...
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/ 0000775 0000000 0000000 00000000000 15135204315 0023503 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0025610 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/aggregation_example.py 0000664 0000000 0000000 00000002415 15135204315 0030061 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Rectangle(Model):
length: float
width: float
rectangles = [
Rectangle(length=0.1, width=1),
Rectangle(length=3.5, width=1),
Rectangle(length=2.87, width=5.19),
Rectangle(length=1, width=10),
Rectangle(length=0.1, width=100),
]
engine = AIOEngine()
await engine.save_all(rectangles)
collection = engine.get_collection(Rectangle)
pipeline = []
# Add an area field
pipeline.append(
{
"$addFields": {
"area": {
"$multiply": [++Rectangle.length, ++Rectangle.width]
} # Compute the area remotely
}
}
)
# Filter only rectanges with an area lower than 10
pipeline.append({"$match": {"area": {"$lt": 10}}})
# Project to keep only the defined fields (this step is optional)
pipeline.append(
{
"$project": {
+Rectangle.length: True,
+Rectangle.width: True,
} # Specifying "area": False is unnecessary here
}
)
documents = await collection.aggregate(pipeline).to_list(length=None)
small_rectangles = [Rectangle.model_validate_doc(doc) for doc in documents]
print(small_rectangles)
#> [
#> Rectangle(id=ObjectId("..."), length=0.1, width=1.0),
#> Rectangle(id=ObjectId("..."), length=3.5, width=1.0),
#> ]
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/collection_name.py 0000664 0000000 0000000 00000000167 15135204315 0027214 0 ustar 00root root 0000000 0000000 from odmantic import Model
class User(Model):
name: str
collection_name = +User
print(collection_name)
#> user
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/create_from_raw.py 0000664 0000000 0000000 00000000506 15135204315 0027215 0 ustar 00root root 0000000 0000000 from bson import ObjectId
from odmantic import Field, Model
class User(Model):
name: str = Field(key_name="username")
document = {"username": "John", "_id": ObjectId("5f8352a87a733b8b18b0cb27")}
user = User.model_validate_doc(document)
print(repr(user))
#> User(id=ObjectId('5f8352a87a733b8b18b0cb27'), name='John')
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/extract_from_existing.py 0000664 0000000 0000000 00000000337 15135204315 0030467 0 ustar 00root root 0000000 0000000 from odmantic import Field, Model
class User(Model):
name: str = Field(key_name="username")
user = User(name="John")
print(user.model_dump_doc())
#> {'username': 'John', '_id': ObjectId('5f8352a87a733b8b18b0cb27')}
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/field_key_name.py 0000664 0000000 0000000 00000000243 15135204315 0027007 0 ustar 00root root 0000000 0000000 from odmantic import Field, Model
class User(Model):
name: str = Field(key_name="username")
print(+User.name)
#> username
print(++User.name)
#> $username
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/motor_collection.py 0000664 0000000 0000000 00000001121 15135204315 0027423 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class User(Model):
name: str
engine = AIOEngine()
motor_collection = engine.get_collection(User)
print(motor_collection)
#> AsyncIOMotorCollection(
#> Collection(
#> Database(
#> MongoClient(
#> host=["localhost:27017"],
#> document_class=dict,
#> tz_aware=False,
#> connect=False,
#> driver=DriverInfo(name="Motor", version="2.2.0", platform="asyncio"),
#> ),
#> "test",
#> ),
#> "user",
#> )
#> )
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/parse_with_unset_default.py 0000664 0000000 0000000 00000000537 15135204315 0031151 0 ustar 00root root 0000000 0000000 from bson import ObjectId
from odmantic import Model
class Player(Model):
name: str
level: int = 1
document = {"name": "Leeroy", "_id": ObjectId("5f8352a87a733b8b18b0cb27")}
user = Player.model_validate_doc(document)
print(repr(user))
#> Player(
#> id=ObjectId("5f8352a87a733b8b18b0cb27"),
#> name="Leeroy",
#> level=1,
#> )
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/parse_with_unset_default_factory.py 0000664 0000000 0000000 00000001230 15135204315 0032667 0 ustar 00root root 0000000 0000000 from datetime import datetime
from bson import ObjectId
from odmantic import Model
from odmantic.exceptions import DocumentParsingError
from odmantic.field import Field
class User(Model):
name: str
created_at: datetime = Field(default_factory=datetime.utcnow)
document = {"name": "Leeroy", "_id": ObjectId("5f8352a87a733b8b18b0cb27")}
try:
User.model_validate_doc(document)
except DocumentParsingError as e:
print(e)
#> 1 validation error for User
#> created_at
#> key not found in document (type=value_error.keynotfoundindocument; key_name='created_at')
#> (User instance details: id=ObjectId('5f8352a87a733b8b18b0cb27'))
parse_with_unset_default_factory_enabled.py 0000664 0000000 0000000 00000001162 15135204315 0034266 0 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/raw_query_usage from datetime import datetime
from bson import ObjectId
from odmantic import Model
from odmantic.exceptions import DocumentParsingError
from odmantic.field import Field
class User(Model):
name: str
updated_at: datetime = Field(default_factory=datetime.utcnow)
model_config = {"parse_doc_with_default_factories": True}
document = {"name": "Leeroy", "_id": ObjectId("5f8352a87a733b8b18b0cb27")}
user = User.model_validate_doc(document)
print(repr(user))
#> User(
#> id=ObjectId("5f8352a87a733b8b18b0cb27"),
#> name="Leeroy",
#> updated_at=datetime.datetime(2020, 11, 8, 23, 28, 19, 980000),
#> )
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/pymongo_collection.py 0000664 0000000 0000000 00000000635 15135204315 0027764 0 ustar 00root root 0000000 0000000 from odmantic import SyncEngine, Model
class User(Model):
name: str
engine = SyncEngine()
collection = engine.get_collection(User)
print(collection)
#> Collection(
#> Database(
#> MongoClient(
#> host=["localhost:27017"],
#> document_class=dict,
#> tz_aware=False,
#> connect=True,
#> ),
#> "test",
#> ),
#> "user",
#> )
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/raw_query_filters.py 0000664 0000000 0000000 00000000170 15135204315 0027621 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Tree(Model):
name: str
average_size: float
engine = AIOEngine()
art049-odmantic-07c27e3/docs/examples_src/raw_query_usage/raw_query_filters_1.py 0000664 0000000 0000000 00000000215 15135204315 0030041 0 ustar 00root root 0000000 0000000 engine.find(Tree, Tree.average_size > 2)
engine.find(Tree, {+Tree.average_size: {"$gt": 2}})
engine.find(Tree, {"average_size": {"$gt": 2}})
art049-odmantic-07c27e3/docs/examples_src/usage_fastapi/ 0000775 0000000 0000000 00000000000 15135204315 0023114 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/usage_fastapi/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0025221 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/usage_fastapi/base_example.py 0000664 0000000 0000000 00000001470 15135204315 0026115 0 ustar 00root root 0000000 0000000 from typing import List
from fastapi import FastAPI, HTTPException
from odmantic import AIOEngine, Model, ObjectId
class Tree(Model):
name: str
average_size: float
discovery_year: int
app = FastAPI()
engine = AIOEngine()
@app.put("/trees/", response_model=Tree)
async def create_tree(tree: Tree):
await engine.save(tree)
return tree
@app.get("/trees/", response_model=List[Tree])
async def get_trees():
trees = await engine.find(Tree)
return trees
@app.get("/trees/count", response_model=int)
async def count_trees():
count = await engine.count(Tree)
return count
@app.get("/trees/{id}", response_model=Tree)
async def get_tree_by_id(id: ObjectId):
tree = await engine.find_one(Tree, Tree.id == id)
if tree is None:
raise HTTPException(404)
return tree
art049-odmantic-07c27e3/docs/examples_src/usage_fastapi/example_delete.py 0000664 0000000 0000000 00000001260 15135204315 0026442 0 ustar 00root root 0000000 0000000 import uvicorn
from fastapi import FastAPI, HTTPException
from odmantic import AIOEngine, Model, ObjectId
class Tree(Model):
name: str
average_size: float
discovery_year: int
app = FastAPI()
engine = AIOEngine()
@app.get("/trees/{id}", response_model=Tree)
async def get_tree_by_id(id: ObjectId):
tree = await engine.find_one(Tree, Tree.id == id)
if tree is None:
raise HTTPException(404)
return tree
@app.delete("/trees/{id}", response_model=Tree)
async def delete_tree_by_id(id: ObjectId):
tree = await engine.find_one(Tree, Tree.id == id)
if tree is None:
raise HTTPException(404)
await engine.delete(tree)
return tree
art049-odmantic-07c27e3/docs/examples_src/usage_fastapi/example_update.py 0000664 0000000 0000000 00000001553 15135204315 0026467 0 ustar 00root root 0000000 0000000 from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from odmantic import AIOEngine, Model, ObjectId
class Tree(Model):
name: str
average_size: float
discovery_year: int
app = FastAPI()
engine = AIOEngine()
@app.get("/trees/{id}", response_model=Tree)
async def get_tree_by_id(id: ObjectId):
tree = await engine.find_one(Tree, Tree.id == id)
if tree is None:
raise HTTPException(404)
return tree
class TreePatchSchema(BaseModel):
name: str = None
average_size: float = None
discovery_year: float = None
@app.patch("/trees/{id}", response_model=Tree)
async def update_tree_by_id(id: ObjectId, patch: TreePatchSchema):
tree = await engine.find_one(Tree, Tree.id == id)
if tree is None:
raise HTTPException(404)
tree.model_update(patch)
await engine.save(tree)
return tree
art049-odmantic-07c27e3/docs/examples_src/usage_pydantic/ 0000775 0000000 0000000 00000000000 15135204315 0023300 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/examples_src/usage_pydantic/__init__.py 0000664 0000000 0000000 00000000015 15135204315 0025405 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-07c27e3/docs/examples_src/usage_pydantic/custom_encoders.py 0000664 0000000 0000000 00000000652 15135204315 0027051 0 ustar 00root root 0000000 0000000 from datetime import datetime
from odmantic.bson import BSON_TYPES_ENCODERS, BaseBSONModel, ObjectId
class M(BaseBSONModel):
id: ObjectId
date: datetime
model_config = {
"json_encoders": {
**BSON_TYPES_ENCODERS,
datetime: lambda dt: dt.year,
}
}
print(M(id=ObjectId(), date=datetime.utcnow()).model_dump_json())
#> {"id": "5fa3378c8fde3766574d874d", "date": 2020}
art049-odmantic-07c27e3/docs/fields.md 0000664 0000000 0000000 00000030536 15135204315 0017413 0 ustar 00root root 0000000 0000000 # Fields
## The `id` field
The [`ObjectId` data type](https://docs.mongodb.com/manual/reference/method/ObjectId/){:target=blank_}
is the default primary key type used by MongoDB. An `ObjectId` comes with many
information embedded into it (timestamp, machine identifier, ...). Since by default,
MongoDB will create a field `_id` containing an `ObjectId` primary key, ODMantic will
bind it automatically to an implicit field named `id`.
```python hl_lines="9 10" linenums="1"
--8<-- "fields/objectid.py"
```
!!! info "ObjectId creation"
This `id` field will be generated on instance creation, before saving the instance
to the database. This helps to keep consistency between the instances persisted to
the database and the ones only created locally.
Even if this behavior is convenient, it is still possible to [define custom primary
keys](#primary-key).
## Field types
### Optional fields
By default, every single field will be required. To specify a field as non-required, the
easiest way is to use the `typing.Optional` generic type that will allow the field to
take the `None` value as well (it will be stored as `null` in the database) and to give
it a default value of `None`.
```python hl_lines="8" linenums="1"
--8<-- "fields/optional.py"
```
### Union fields
As explained in the [Python Typing
documentation](https://docs.python.org/3/library/typing.html#typing.Optional){:target=bank_},
`Optional[X]` is equivalent to `Union[X, None]`. That implies that the field type will
be either `X` or `None`.
It's possible to combine any kind of type using the `typîng.Union` type constructor. For
example if we want to allow both `string` and `int` in a field:
```python hl_lines="7" linenums="1"
--8<-- "fields/union.py"
```
!!! question "NoneType"
Internally python describes the type of the `None` object as `NoneType` but in
practice, `None` is used directly in type annotations ([more details](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#optional-types-and-the-none-type){:target=bank_}).
### Enum fields
To define choices, it's possible to use the standard `enum` classes:
```python hl_lines="6-8 13" linenums="1"
--8<-- "fields/enum.py"
```
!!! abstract "Resulting documents in the collection `tree` after execution"
```json hl_lines="7"
{ "_id" : ObjectId("5f818f2dd5708527282c49b6"), "kind" : "big", "name" : "Sequoia" }
{ "_id" : ObjectId("5f818f2dd5708527282c49b7"), "kind" : "small", "name" : "Spruce" }
```
If you try to use a value not present in the allowed choices, a [ValidationError](https://docs.pydantic.dev/latest/usage/models/#error-handling){:target=blank_} exception will be raised.
!!! warning "Usage of `enum.auto`"
If you might add some values to an `Enum`, it's strongly recommended not to use the
`enum.auto` value generator. Depending on the order you add choices, it could
completely break the consistency with documents stored in the database.
??? example "Unwanted behavior example"
```python hl_lines="11-12" linenums="1"
--8<-- "fields/inconsistent_enum_1.py"
```
```python hl_lines="6 12-15" linenums="1"
--8<-- "fields/inconsistent_enum_2.py"
```
### Container fields
#### List
```python linenums="1"
--8<-- "fields/container_list.py"
```
!!! tip
It's possible to define element count constraints for a list field using the
[Field][odmantic.field.Field] descriptor.
#### Tuple
```python linenums="1"
--8<-- "fields/container_tuple.py"
```
#### Dict
!!! tip
For mapping types with already known keys, you can see the [embedded models
section](modeling.md#embedded-models).
```python linenums="1"
--8<-- "fields/container_dict.py"
```
!!! tip "Performance tip"
Whenever possible, try to avoid mutable container types (`List`, `Set`, ...) and
prefer their Immutable alternatives (`Tuple`, `FrozenSet`, ...). This will allow
ODMantic to speedup database writes by only saving the modified container fields.
### `BSON` types integration
ODMantic supports native python BSON types ([`bson`
package](https://api.mongodb.com/python/current/api/bson/index.html){:target=blank_}).
Those types can be used directly as field types:
- [`bson.ObjectId`](https://api.mongodb.com/python/current/api/bson/objectid.html){:target=blank_}
- [`bson.Int64`](https://api.mongodb.com/python/current/api/bson/int64.html){:target=blank_}
- [`bson.Decimal128`](https://api.mongodb.com/python/current/api/bson/decimal128.html){:target=blank_}
- [`bson.Regex`](https://api.mongodb.com/python/current/api/bson/regex.html){:target=blank_}
- [`bson.Binary`](https://api.mongodb.com/python/current/api/bson/binary.html#bson.binary.Binary){:target=blank_}
??? info "Generic python to BSON type map"
| Python type | BSON type | Comment |
| ---------------------- | :--------: | ------------------------------------------------------------ |
| `bson.ObjectId` | `objectId` |
| `bool` | `bool` | |
| `int` | `int` | value between -2^31 and 2^31 - 1 |
| `int` | `long` | value not between -2^31 and 2^31 - 1 |
| `bson.Int64` | `long` |
| `float` | `double` |
| `bson.Decimal128` | `decimal` | |
| `decimal.Decimal` | `decimal` |
| `str` | `string` |
| `typing.Pattern` | `regex` |
| `bson.Regex` | `regex` |
| `bytes` | `binData` |
| `bson.Binary` | `binData` |
| `datetime.datetime` | `date` | microseconds are truncated, only naive datetimes are allowed |
| `typing.Dict` | `object` |
| `typing.List` | `array` |
| `typing.Sequence` | `array` |
| `typing.Tuple[T, ...]` | `array` |
### Pydantic fields
Most of the types supported by pydantic are supported by ODMantic. See [pydantic:
Field Types](https://docs.pydantic.dev/latest/usage/types/types/){:target=bank_} for more
field types.
Unsupported fields:
- `typing.Callable`
Fields with a specific behavior:
- `datetime.datetime`: Only [naive datetime
objects](https://docs.python.org/3/library/datetime.html#determining-if-an-object-is-aware-or-naive){:target=blank_}
will be allowed as MongoDB doesn't store the timezone information. Also, the
microsecond information will be truncated.
## Customization
The field customization can mainly be performed using the [Field][odmantic.field.Field]
descriptor. This descriptor is here to define everything about the field except its
type.
### Default values
The easiest way to set a default value to a field is by assigning this default value
directly while defining the model.
```python hl_lines="6" linenums="1"
--8<-- "fields/default_value.py"
```
You can combine default values and an existing [Field][odmantic.field.Field]
descriptor using the `default` keyword argument.
``` python hl_lines="6" linenums="1"
--8<-- "fields/default_value_field.py"
```
!!! info "Default factory"
You may as well define a factory function instead of a value using the
`default_factory` argument of the [Field][odmantic.field.Field] descriptor.
By default, the default factories won't be used while parsing MongoDB documents.
It's possible to enable this behavior with the `parse_doc_with_default_factories`
[Config](modeling.md#advanced-configuration) option.
!!! warning "Default values validation"
Currently the default values are not validated yet during the model creation.
An inconsistent default value might raise a
[ValidationError](https://docs.pydantic.dev/latest/usage/models/#error-handling){:target=blank_}
while building an instance.
### Document structure
By default, the MongoDB documents fields will be named after the field name. It is
possible to override this naming policy by using the `key_name` argument in the
[Field][odmantic.field.Field] descriptor.
{{ async_sync_snippet("fields", "custom_key_name.py", hl_lines="5") }}
!!! abstract "Resulting documents in the collection `player` after execution"
```json hl_lines="3"
{
"_id": ObjectId("5ed50fcad11d1975aa3d7a28"),
"username": "Jack",
}
```
See [this section](#the-id-field) for more details about the `_id` field that has been added.
### Primary key
While ODMantic will by default populate the `id` field as a primary key, you can use any
other field as the primary key.
{{ async_sync_snippet("fields", "custom_primary_field.py", hl_lines="5") }}
!!! abstract "Resulting documents in the collection `player` after execution"
```json
{
"_id": "Leeroy Jenkins"
}
```
!!! info
The Mongo name of the primary field will be enforced to `_id` and you will not be
able to change it.
!!! warning
Using mutable types (Set, List, ...) as primary field might result in inconsistent
behaviors.
### Indexed fields
You can define an index on a single field by using the `index` argument of the
[Field][odmantic.field.Field] descriptor.
More details about index creation can be found in the
[Indexes](modeling.md#indexes) section.
{{ async_sync_snippet("fields", "indexed_field.py", hl_lines="6 10") }}
!!! warning
When using indexes, make sure to call the `configure_database` method
([AIOEngine.configure_database][odmantic.engine.AIOEngine.configure_database] or
[SyncEngine.configure_database][odmantic.engine.SyncEngine.configure_database]) to
persist the indexes to the database.
### Unique fields
In the same way, you can define unique constrains on a single field by using the
`unique` argument of the [Field][odmantic.field.Field] descriptor. This will ensure that
values of this fields are unique among all the instances saved in the database.
More details about unique index creation can be found in the
[Indexes](modeling.md#indexes) section.
{{ async_sync_snippet("fields", "unique_field.py", hl_lines="5 9 15-18") }}
!!! warning
When using indexes, make sure to call the `configure_database` method
([AIOEngine.configure_database][odmantic.engine.AIOEngine.configure_database] or
[SyncEngine.configure_database][odmantic.engine.SyncEngine.configure_database]) to
persist the indexes to the database.
## Validation
As ODMantic strongly relies on pydantic when it comes to data validation, most of the
validation features provided by pydantic are available:
- Add field validation constraints by using the [Field descriptor][odmantic.field.Field]
```python linenums="1"
--8<-- "fields/validation_field_descriptor.py"
```
- Use strict types to prevent to coercion from compatible types ([pydantic: Strict Types](https://docs.pydantic.dev/latest/usage/types/strict_types/){:target=blank_})
```python linenums="1"
--8<-- "fields/validation_strict_types.py"
```
- Define custom field validators ([pydantic:
Validators](https://docs.pydantic.dev/latest/usage/validators/){:target=blank_})
```python linenums="1"
--8<-- "fields/custom_field_validators.py"
```
- Define custom model validators: [more details](modeling.md#custom-model-validators)
## Custom field types
Exactly in the same way pydantic allows it, it's possible to define custom field types as well with ODMantic ([Pydantic: Custom data types](https://docs.pydantic.dev/latest/concepts/types/#custom-types){:target=blank_}).
Sometimes, it might be required to customize as well the field BSON serialization. In
order to do this, the field class will have to implement the `__bson__` class method.
```python linenums="1" hl_lines="11-12 20-24 27-29 32"
--8<-- "fields/custom_bson_serialization.py"
```
In this example, we decide to store string data manually encoded in the ASCII encoding.
The encoding is handled in the `__bson__` class method. On top of this, we handle the
decoding by attempting to decode `bytes` object in the `validate` method.
!!! abstract "Resulting documents in the collection `example` after execution"
```json hl_lines="3"
{
"_id" : ObjectId("5f81fa5e8adaf4bf33f05035"),
"field" : BinData(0,"aGVsbG8gd29ybGQ=")
}
```
!!! warning
When using custom bson serialization, it's important to handle as well the data
validation for data retrieved from Mongo. In the previous example it's done by
handling `bytes` objects in the validate method.
art049-odmantic-07c27e3/docs/img/ 0000775 0000000 0000000 00000000000 15135204315 0016370 5 ustar 00root root 0000000 0000000 art049-odmantic-07c27e3/docs/img/internals.excalidraw 0000664 0000000 0000000 00000310433 15135204315 0022440 0 ustar 00root root 0000000 0000000 {
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [
{
"type": "rectangle",
"version": 147,
"versionNonce": 128764279,
"isDeleted": false,
"id": "1QClEzXygZTqt76PxkWkH",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 146.65989685058588,
"y": 30.25213623046875,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 238,
"height": 50,
"seed": 1347042135,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "yfG0e_3Wa-DCZDcem7x8q"
},
{
"id": "UHKGbTMVCX3xV-2j2EI8K",
"type": "arrow"
},
{
"id": "lD4XdHeiTgVFnGz0vty89",
"type": "arrow"
},
{
"id": "syo0XVss1rjwOn1YisePL",
"type": "arrow"
}
],
"updated": 1688229366345,
"link": null,
"locked": false
},
{
"type": "text",
"version": 94,
"versionNonce": 1173301423,
"isDeleted": false,
"id": "yfG0e_3Wa-DCZDcem7x8q",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 163.20000457763666,
"y": 42.75213623046875,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 204.91978454589844,
"height": 25,
"seed": 184948823,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214155,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "BaseModelMetaclass",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "1QClEzXygZTqt76PxkWkH",
"originalText": "BaseModelMetaclass",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 207,
"versionNonce": 2017728439,
"isDeleted": false,
"id": "_ARtVrDLJfSitpVtrp3xr",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 47.72650146484375,
"y": 330.26622772216797,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 158,
"height": 48,
"seed": 1927784151,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "nHwV4fYy_yVG7Loh6-ngL"
},
{
"id": "FEowXgbNW5ved7spvvO41",
"type": "arrow"
},
{
"id": "yg9fb8SkwTr4SYRdt2ZhT",
"type": "arrow"
}
],
"updated": 1688229366345,
"link": null,
"locked": false
},
{
"type": "text",
"version": 209,
"versionNonce": 846740929,
"isDeleted": false,
"id": "nHwV4fYy_yVG7Loh6-ngL",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 99.73652648925781,
"y": 341.76622772216797,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 53.979949951171875,
"height": 25,
"seed": 310016823,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214155,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Model",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "_ARtVrDLJfSitpVtrp3xr",
"originalText": "Model",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 41,
"versionNonce": 93115895,
"isDeleted": false,
"id": "kWjrydqFbA0BV-hc-4YBW",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 119.88507843017578,
"y": -86.73246765136719,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 286.52425384521484,
"height": 60,
"seed": 1054861143,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "aml_HLVWOSo7schAkrqq6"
},
{
"id": "UHKGbTMVCX3xV-2j2EI8K",
"type": "arrow"
}
],
"updated": 1688229366346,
"link": null,
"locked": false
},
{
"type": "text",
"version": 28,
"versionNonce": 2122595023,
"isDeleted": false,
"id": "aml_HLVWOSo7schAkrqq6",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 185.52728652954102,
"y": -81.73246765136719,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 155.23983764648438,
"height": 50,
"seed": 1144583831,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214155,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "\nModelMetaclass",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "kWjrydqFbA0BV-hc-4YBW",
"originalText": "\nModelMetaclass",
"lineHeight": 1.25,
"baseline": 44
},
{
"type": "rectangle",
"version": 64,
"versionNonce": 1200422679,
"isDeleted": false,
"id": "ZYQVZcX3tXLVZAnH3hWx0",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 12.148128509521484,
"y": 140.1332015991211,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 212.24032592773438,
"height": 48.530487060546875,
"seed": 544714647,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "yWHeF-rNpJgjzhvd-MSa7"
},
{
"id": "lD4XdHeiTgVFnGz0vty89",
"type": "arrow"
},
{
"id": "yg9fb8SkwTr4SYRdt2ZhT",
"type": "arrow"
}
],
"updated": 1688229366346,
"link": null,
"locked": false
},
{
"type": "text",
"version": 57,
"versionNonce": 1192025505,
"isDeleted": false,
"id": "yWHeF-rNpJgjzhvd-MSa7",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 40.648372650146484,
"y": 151.89844512939453,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 155.23983764648438,
"height": 25,
"seed": 760087769,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214156,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ModelMetaclass",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "ZYQVZcX3tXLVZAnH3hWx0",
"originalText": "ModelMetaclass",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 116,
"versionNonce": 1709900855,
"isDeleted": false,
"id": "o0iF0wULG3DdAi8FGxZZf",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 281.195556640625,
"y": 133.90399932861328,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 264,
"height": 54,
"seed": 1122191513,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "7JDlJOsCac6X1GeJdx5QI"
},
{
"id": "syo0XVss1rjwOn1YisePL",
"type": "arrow"
},
{
"id": "ZjJ6g8_13kITs2JvRY1y9",
"type": "arrow"
}
],
"updated": 1688229366346,
"link": null,
"locked": false
},
{
"type": "text",
"version": 105,
"versionNonce": 586287343,
"isDeleted": false,
"id": "7JDlJOsCac6X1GeJdx5QI",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 289.41568756103516,
"y": 148.40399932861328,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 247.5597381591797,
"height": 25,
"seed": 205561529,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214156,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "EmbeddedModelMetaclass",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "o0iF0wULG3DdAi8FGxZZf",
"originalText": "EmbeddedModelMetaclass",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 171,
"versionNonce": 1155523887,
"isDeleted": false,
"id": "jABrwBukq5wQ1d4yfmmyl",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -237.8904549734932,
"y": 322.25397455124636,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 205,
"height": 54,
"seed": 492005815,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "RQUSItVO1nOHRaqYqS7ZY"
},
{
"id": "sl8bKzR824MnizHKNPVaW",
"type": "arrow"
},
{
"id": "FEowXgbNW5ved7spvvO41",
"type": "arrow"
},
{
"id": "nbeiWW2Hs9fQhvAnNN90y",
"type": "arrow"
}
],
"updated": 1688305624281,
"link": null,
"locked": false
},
{
"type": "text",
"version": 160,
"versionNonce": 1700759873,
"isDeleted": false,
"id": "RQUSItVO1nOHRaqYqS7ZY",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -218.20038386753617,
"y": 336.75397455124636,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 165.61985778808594,
"height": 25,
"seed": 130512727,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305624281,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "_BaseODMModel",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "jABrwBukq5wQ1d4yfmmyl",
"originalText": "_BaseODMModel",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 202,
"versionNonce": 799201711,
"isDeleted": false,
"id": "W9rThSS1FY0WVyk6rE3OQ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -235.49182619367303,
"y": 68.70132627941314,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 202,
"height": 60,
"seed": 1054861143,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "yaA5kLOeNzHhuCdH5RbV6"
},
{
"id": "JbYMZWhuwFmFeiaAYZsZI",
"type": "arrow"
},
{
"id": "sl8bKzR824MnizHKNPVaW",
"type": "arrow"
},
{
"id": "Jbcd5LEPNRJ78cK3nbitw",
"type": "arrow"
}
],
"updated": 1688305650377,
"link": null,
"locked": false
},
{
"type": "text",
"version": 157,
"versionNonce": 1151383809,
"isDeleted": false,
"id": "yaA5kLOeNzHhuCdH5RbV6",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -186.321774618966,
"y": 73.70132627941314,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 103.65989685058594,
"height": 50,
"seed": 1144583831,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305621386,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "\nBaseModel",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "W9rThSS1FY0WVyk6rE3OQ",
"originalText": "\nBaseModel",
"lineHeight": 1.25,
"baseline": 44
},
{
"type": "arrow",
"version": 145,
"versionNonce": 693451671,
"isDeleted": false,
"id": "UHKGbTMVCX3xV-2j2EI8K",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 262.1915314558535,
"y": -23.687198401304528,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 1.692945786175244,
"height": 50.53572173519619,
"seed": 1001008823,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366347,
"link": null,
"locked": false,
"startBinding": {
"elementId": "kWjrydqFbA0BV-hc-4YBW",
"gap": 3.0452692500626695,
"focus": 0.01588143459831425
},
"endBinding": {
"elementId": "1QClEzXygZTqt76PxkWkH",
"gap": 3.4036128965770875,
"focus": -0.00687513575365181
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
1.692945786175244,
50.53572173519619
]
]
},
{
"type": "arrow",
"version": 152,
"versionNonce": 777989465,
"isDeleted": false,
"id": "lD4XdHeiTgVFnGz0vty89",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 251.36881409019264,
"y": 81.25213623046875,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 136.5801048833885,
"height": 56.546383626290464,
"seed": 1382775159,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366347,
"link": null,
"locked": false,
"startBinding": {
"elementId": "1QClEzXygZTqt76PxkWkH",
"gap": 1,
"focus": -0.26828215886152174
},
"endBinding": {
"elementId": "ZYQVZcX3tXLVZAnH3hWx0",
"gap": 2.3346817423618815,
"focus": -0.4111471332894141
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-136.5801048833885,
56.546383626290464
]
]
},
{
"type": "arrow",
"version": 279,
"versionNonce": 1282912439,
"isDeleted": false,
"id": "syo0XVss1rjwOn1YisePL",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 289.1718950933908,
"y": 81.25213623046875,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 139.84882152444192,
"height": 51.55749759661953,
"seed": 991131415,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366347,
"link": null,
"locked": false,
"startBinding": {
"elementId": "1QClEzXygZTqt76PxkWkH",
"gap": 1,
"focus": 0.2483785002338254
},
"endBinding": {
"elementId": "o0iF0wULG3DdAi8FGxZZf",
"gap": 1.0943655015249987,
"focus": 0.4484114755079205
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
139.84882152444192,
51.55749759661953
]
]
},
{
"type": "arrow",
"version": 119,
"versionNonce": 1571033999,
"isDeleted": false,
"id": "JbYMZWhuwFmFeiaAYZsZI",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 124.82330322265682,
"y": -36.5750681559245,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 237.97867673902235,
"height": 104.27639443533764,
"seed": 1210082841,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305621387,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": {
"elementId": "W9rThSS1FY0WVyk6rE3OQ",
"gap": 1,
"focus": -0.29157207339294644
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-237.97867673902235,
104.27639443533764
]
]
},
{
"type": "arrow",
"version": 393,
"versionNonce": 1174321441,
"isDeleted": false,
"id": "sl8bKzR824MnizHKNPVaW",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -143.30629708519024,
"y": 129.70132627941314,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 4.969637552835366,
"height": 191.55264827183322,
"seed": 1171082935,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305624281,
"link": null,
"locked": false,
"startBinding": {
"elementId": "W9rThSS1FY0WVyk6rE3OQ",
"gap": 1,
"focus": 0.07965115605855717
},
"endBinding": {
"elementId": "jABrwBukq5wQ1d4yfmmyl",
"gap": 1,
"focus": -0.13189773975727065
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-4.969637552835366,
191.55264827183322
]
]
},
{
"type": "arrow",
"version": 338,
"versionNonce": 1324150017,
"isDeleted": false,
"id": "FEowXgbNW5ved7spvvO41",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -31.890454973493206,
"y": 350.60417211217793,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 75.49999103848879,
"height": 1.7022986889428466,
"seed": 376203863,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305624281,
"link": null,
"locked": false,
"startBinding": {
"elementId": "jABrwBukq5wQ1d4yfmmyl",
"gap": 1,
"focus": 0.15412681829980826
},
"endBinding": {
"elementId": "_ARtVrDLJfSitpVtrp3xr",
"gap": 4.11696539984818,
"focus": 0.2807623067766991
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
75.49999103848879,
-1.7022986889428466
]
]
},
{
"type": "arrow",
"version": 250,
"versionNonce": 1980342265,
"isDeleted": false,
"id": "yg9fb8SkwTr4SYRdt2ZhT",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 123.39449550552001,
"y": 191.66057417127817,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 3.6081141666376197,
"height": 134.99102783203116,
"seed": 1520524375,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366347,
"link": null,
"locked": false,
"startBinding": {
"elementId": "ZYQVZcX3tXLVZAnH3hWx0",
"gap": 2.9968855116101922,
"focus": -0.05478211135391337
},
"endBinding": {
"elementId": "_ARtVrDLJfSitpVtrp3xr",
"gap": 3.614625718858595,
"focus": -0.09640979145820627
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-3.6081141666376197,
134.99102783203116
]
]
},
{
"type": "rectangle",
"version": 271,
"versionNonce": 14366743,
"isDeleted": false,
"id": "L26M6KL304ntTfn0ENqja",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 351.80155944824253,
"y": 431.6505215962727,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 174,
"height": 51,
"seed": 1927784151,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "ZhK_hxQ00cq9zyBrZVVvp"
},
{
"id": "nbeiWW2Hs9fQhvAnNN90y",
"type": "arrow"
},
{
"id": "ZjJ6g8_13kITs2JvRY1y9",
"type": "arrow"
}
],
"updated": 1688229366347,
"link": null,
"locked": false
},
{
"type": "text",
"version": 285,
"versionNonce": 98085217,
"isDeleted": false,
"id": "ZhK_hxQ00cq9zyBrZVVvp",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 365.65163421630893,
"y": 444.6505215962727,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 146.2998504638672,
"height": 25,
"seed": 310016823,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214157,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "EmbeddedModel",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "L26M6KL304ntTfn0ENqja",
"originalText": "EmbeddedModel",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 278,
"versionNonce": 672588431,
"isDeleted": false,
"id": "nbeiWW2Hs9fQhvAnNN90y",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -136.22835885326396,
"y": 380.7382318405877,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 475.8425232056813,
"height": 83.21626519297138,
"seed": 1837564215,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305634035,
"link": null,
"locked": false,
"startBinding": {
"elementId": "jABrwBukq5wQ1d4yfmmyl",
"focus": 0.21356247302120304,
"gap": 4.484257289341315
},
"endBinding": {
"elementId": "L26M6KL304ntTfn0ENqja",
"focus": -0.30169734772557727,
"gap": 12.187395095825195
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
64.00875000142305,
78.19504220145092
],
[
475.8425232056813,
83.21626519297138
]
]
},
{
"type": "arrow",
"version": 331,
"versionNonce": 296835961,
"isDeleted": false,
"id": "ZjJ6g8_13kITs2JvRY1y9",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 419.94624605978356,
"y": 190.21559320882392,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 6.909197352382307,
"height": 235.32681525751508,
"seed": 457242297,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366348,
"link": null,
"locked": false,
"startBinding": {
"elementId": "o0iF0wULG3DdAi8FGxZZf",
"gap": 2.311593880210631,
"focus": -0.04437565916514326
},
"endBinding": {
"elementId": "L26M6KL304ntTfn0ENqja",
"gap": 6.108113129933705,
"focus": -0.12556429943324393
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
6.909197352382307,
235.32681525751508
]
]
},
{
"type": "rectangle",
"version": 54,
"versionNonce": 557773975,
"isDeleted": false,
"id": "jWOGPtqhRJMccvPdX-VDu",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 907.7460937500006,
"y": -34.374796549479186,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 315,
"height": 45,
"seed": 138321879,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "Nl6TQd333DHEPY_TzDyPR"
},
{
"id": "36cRmVAauYIpgPT494cX6",
"type": "arrow"
},
{
"id": "ts2am8lOYt8dakA2vqYPz",
"type": "arrow"
}
],
"updated": 1688229366348,
"link": null,
"locked": false
},
{
"type": "text",
"version": 39,
"versionNonce": 1617549615,
"isDeleted": false,
"id": "Nl6TQd333DHEPY_TzDyPR",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1011.6561508178717,
"y": -24.374796549479186,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 107.17988586425781,
"height": 25,
"seed": 803237785,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214157,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "BaseEngine",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "jWOGPtqhRJMccvPdX-VDu",
"originalText": "BaseEngine",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 55,
"versionNonce": 1891781047,
"isDeleted": false,
"id": "nNYXFkQd-Hh6FiZgZBZwu",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 758.7050781250003,
"y": 183.12886555989576,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 250,
"height": 40,
"seed": 1288696503,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "9OJ530C0zfIlBTgPyonk_"
},
{
"id": "36cRmVAauYIpgPT494cX6",
"type": "arrow"
}
],
"updated": 1688229366348,
"link": null,
"locked": false
},
{
"type": "text",
"version": 51,
"versionNonce": 379508033,
"isDeleted": false,
"id": "9OJ530C0zfIlBTgPyonk_",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 835.6851196289066,
"y": 190.62886555989576,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 96.0399169921875,
"height": 25,
"seed": 1127779255,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214158,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "AIOEngine",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "nNYXFkQd-Hh6FiZgZBZwu",
"originalText": "AIOEngine",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 96,
"versionNonce": 1532885719,
"isDeleted": false,
"id": "WGJmvq-xHSMb702lCHnMC",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1167.1317749023438,
"y": 170.93236287434888,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 250,
"height": 50,
"seed": 1288696503,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "jJvJPzsmAWxAUGEcG9nuU"
},
{
"id": "ts2am8lOYt8dakA2vqYPz",
"type": "arrow"
}
],
"updated": 1688229366348,
"link": null,
"locked": false
},
{
"type": "text",
"version": 102,
"versionNonce": 1196743503,
"isDeleted": false,
"id": "jJvJPzsmAWxAUGEcG9nuU",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1242.9218215942383,
"y": 183.43236287434888,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 98.41990661621094,
"height": 25,
"seed": 1127779255,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214158,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "SyncEngine",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "WGJmvq-xHSMb702lCHnMC",
"originalText": "SyncEngine",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 124,
"versionNonce": 363235319,
"isDeleted": false,
"id": "36cRmVAauYIpgPT494cX6",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1022.6256913695252,
"y": 13.66178581311307,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 162.944155964238,
"height": 167.3538347346252,
"seed": 918387319,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366349,
"link": null,
"locked": false,
"startBinding": {
"elementId": "jWOGPtqhRJMccvPdX-VDu",
"gap": 1.0365823625922548,
"focus": 0.10395625252674856
},
"endBinding": {
"elementId": "nNYXFkQd-Hh6FiZgZBZwu",
"gap": 2.1132450121574724,
"focus": -0.3153122914145206
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-162.944155964238,
167.3538347346252
]
]
},
{
"type": "arrow",
"version": 121,
"versionNonce": 1675581177,
"isDeleted": false,
"id": "ts2am8lOYt8dakA2vqYPz",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1115.0976562500005,
"y": 13.601826985677064,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 176.01424666596154,
"height": 152.80197552696214,
"seed": 2142648633,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366349,
"link": null,
"locked": false,
"startBinding": {
"elementId": "jWOGPtqhRJMccvPdX-VDu",
"focus": -0.11179254674682215,
"gap": 2.97662353515625
},
"endBinding": {
"elementId": "WGJmvq-xHSMb702lCHnMC",
"focus": 0.21453104117484706,
"gap": 4.52856036170968
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
176.01424666596154,
152.80197552696214
]
]
},
{
"type": "rectangle",
"version": 71,
"versionNonce": 2045400919,
"isDeleted": false,
"id": "NXn4aHdtl130ZdWi0rxEa",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -286.69224548339787,
"y": 854.8295796712239,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 260,
"height": 71,
"seed": 1721253785,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "-T0iQUOE9JeAMcsxBmAvp"
},
{
"id": "UAktyDZl4twNnjsg7yU4H",
"type": "arrow"
}
],
"updated": 1688229366349,
"link": null,
"locked": false
},
{
"type": "text",
"version": 75,
"versionNonce": 695502113,
"isDeleted": false,
"id": "-T0iQUOE9JeAMcsxBmAvp",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -272.7221374511713,
"y": 877.8295796712239,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 232.05978393554688,
"height": 25,
"seed": 1623944951,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214158,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMBaseIndexableField",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "NXn4aHdtl130ZdWi0rxEa",
"originalText": "ODMBaseIndexableField",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 88,
"versionNonce": 1265849463,
"isDeleted": false,
"id": "OZ8QhmTUsd704jVLu1udO",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -648.5759201049798,
"y": -103.5347811381022,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 142,
"height": 35,
"seed": 981057847,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "XP6YoNaNuibiKtzG1WK7k"
}
],
"updated": 1688229366349,
"link": null,
"locked": false
},
{
"type": "text",
"version": 81,
"versionNonce": 970627439,
"isDeleted": false,
"id": "XP6YoNaNuibiKtzG1WK7k",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -628.205863952636,
"y": -98.5347811381022,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 101.2598876953125,
"height": 25,
"seed": 2053503095,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214159,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Metaclass",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "OZ8QhmTUsd704jVLu1udO",
"originalText": "Metaclass",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 111,
"versionNonce": 958377367,
"isDeleted": false,
"id": "S4uDiqJBBlth1jVYRXYdQ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -648.3649291992181,
"y": -48.75762049357098,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 183,
"height": 35,
"seed": 2109010743,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "NloV4MhszJYXPo8tE2uM3"
}
],
"updated": 1688229366349,
"link": null,
"locked": false
},
{
"type": "text",
"version": 104,
"versionNonce": 1506359553,
"isDeleted": false,
"id": "NloV4MhszJYXPo8tE2uM3",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -632.8248443603509,
"y": -43.75762049357098,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 151.91983032226562,
"height": 25,
"seed": 1140701367,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214159,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Abstract Class",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "S4uDiqJBBlth1jVYRXYdQ",
"originalText": "Abstract Class",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 79,
"versionNonce": 505668279,
"isDeleted": false,
"id": "1Ms-vOWh7lMrB2keF4ntc",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -651.2998962402337,
"y": 4.0566190083821425,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 174.24031066894526,
"height": 36.986572265625,
"seed": 1982922103,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "uHyKoVLvjUdeLLMKPUkSw"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 57,
"versionNonce": 1833145231,
"isDeleted": false,
"id": "uHyKoVLvjUdeLLMKPUkSw",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -590.779708862304,
"y": 10.049905141194643,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 53.19993591308594,
"height": 25,
"seed": 1718751831,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214159,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Class",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "1Ms-vOWh7lMrB2keF4ntc",
"originalText": "Class",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 87,
"versionNonce": 2065986519,
"isDeleted": false,
"id": "R4MYkAZlM4JZsv6E_OTSr",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -684.7675056457513,
"y": -184.832098642985,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 266.6105995178222,
"height": 528.6784133911133,
"seed": 228259801,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 38,
"versionNonce": 1926936948,
"isDeleted": false,
"id": "tNFFZPTSeL4oBQKV6xwlb",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -641.9628753662103,
"y": -155.60393397013345,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 72.17991638183594,
"height": 25,
"seed": 1453579833,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1702173464927,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Caption",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Caption",
"lineHeight": 1.25,
"baseline": 17
},
{
"type": "rectangle",
"version": 14,
"versionNonce": 1733168633,
"isDeleted": false,
"id": "Q4CGBEw8PIYjICcvxNOra",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -284.2282104492182,
"y": 693.6255086263019,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 252.154296875,
"height": 72.44720458984375,
"seed": 1408485015,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "5n6xTfbQVrPsFxfM88e02"
},
{
"id": "UAktyDZl4twNnjsg7yU4H",
"type": "arrow"
},
{
"id": "mSJfnHWBSTvBtjdyV0O1u",
"type": "arrow"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 6,
"versionNonce": 1506298287,
"isDeleted": false,
"id": "5n6xTfbQVrPsFxfM88e02",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -227.4309997558588,
"y": 717.3491109212238,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 138.55987548828125,
"height": 25,
"seed": 2119975703,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214160,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMBaseField",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "Q4CGBEw8PIYjICcvxNOra",
"originalText": "ODMBaseField",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 23,
"versionNonce": 1647044313,
"isDeleted": false,
"id": "UAktyDZl4twNnjsg7yU4H",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -159.76171874999943,
"y": 768.2135213216143,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 0.45587158203125,
"height": 84.97824096679688,
"seed": 876963321,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": {
"elementId": "Q4CGBEw8PIYjICcvxNOra",
"focus": 0.011125618553601228,
"gap": 2.1408081054686363
},
"endBinding": {
"elementId": "NXn4aHdtl130ZdWi0rxEa",
"focus": -0.02860865318629802,
"gap": 1.6378173828127274
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-0.45587158203125,
84.97824096679688
]
]
},
{
"type": "rectangle",
"version": 167,
"versionNonce": 336416567,
"isDeleted": false,
"id": "MmjpQNm8e4X6meZXbUBEd",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -410.88523864746037,
"y": 1014.4134267171222,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 199,
"height": 42,
"seed": 109882233,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "Q4QCGNzTUwlJqLG19obAC"
},
{
"id": "SJolbrAOGVVcoqvsFrJTh",
"type": "arrow"
},
{
"id": "2pkhcWk_2M8N6sAuVG7xG",
"type": "arrow"
},
{
"id": "RA_WqOlwJvd3VGyVXeGOC",
"type": "arrow"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 187,
"versionNonce": 1023257793,
"isDeleted": false,
"id": "Q4QCGNzTUwlJqLG19obAC",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -355.82520294189396,
"y": 1022.9134267171221,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 88.87992858886719,
"height": 25,
"seed": 453649367,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214160,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMField",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "MmjpQNm8e4X6meZXbUBEd",
"originalText": "ODMField",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 166,
"versionNonce": 163628185,
"isDeleted": false,
"id": "gIeBiC2fqLAh_jzsGEknB",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 35.91697311401413,
"y": 972.5399525960286,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 199,
"height": 42,
"seed": 109882233,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "nI1YmXmWvPmgm9-oUxvxi"
},
{
"id": "mSJfnHWBSTvBtjdyV0O1u",
"type": "arrow"
},
{
"id": "rypSwXtCbrn6iaxCvcrsP",
"type": "arrow"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 198,
"versionNonce": 1553021903,
"isDeleted": false,
"id": "nI1YmXmWvPmgm9-oUxvxi",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 65.18703842163131,
"y": 981.0399525960286,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 140.45986938476562,
"height": 25,
"seed": 453649367,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214160,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMReference",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "gIeBiC2fqLAh_jzsGEknB",
"originalText": "ODMReference",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 69,
"versionNonce": 8991321,
"isDeleted": false,
"id": "mSJfnHWBSTvBtjdyV0O1u",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -25.968200683593295,
"y": 732.8055928548174,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 173.053466796875,
"height": 230.97659301757812,
"seed": 1892089721,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": {
"elementId": "Q4CGBEw8PIYjICcvxNOra",
"focus": -0.4216852712060308,
"gap": 6.105712890624886
},
"endBinding": {
"elementId": "gIeBiC2fqLAh_jzsGEknB",
"focus": 0.14721052685104474,
"gap": 8.75776672363304
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
151.14483642578125,
34.8729248046875
],
[
173.053466796875,
230.97659301757812
]
]
},
{
"type": "rectangle",
"version": 47,
"versionNonce": 88309561,
"isDeleted": false,
"id": "oBN5q2AVj21FpaTsmD9Up",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -575.4466247558588,
"y": 1141.5685628255205,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 232.43093872070312,
"height": 43.39898681640625,
"seed": 726003001,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "EwO5uCo0b84wFobhqhzKa"
},
{
"id": "a1ge-330toFLQcoB9JwZN",
"type": "arrow"
},
{
"id": "ZrKEOJwFCNqKazB6CJahM",
"type": "arrow"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 35,
"versionNonce": 1561095329,
"isDeleted": false,
"id": "EwO5uCo0b84wFobhqhzKa",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -528.111091613769,
"y": 1150.7680562337237,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 137.75987243652344,
"height": 25,
"seed": 1577305783,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214160,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMEmbedded",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "oBN5q2AVj21FpaTsmD9Up",
"originalText": "ODMEmbedded",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 49,
"versionNonce": 450965529,
"isDeleted": false,
"id": "a1ge-330toFLQcoB9JwZN",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -316.9401245117182,
"y": 1055.1209615071612,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 112.46305600605285,
"height": 83.99490356445312,
"seed": 1146642359,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": {
"elementId": "oBN5q2AVj21FpaTsmD9Up",
"focus": -0.01727856974303873,
"gap": 2.45269775390625
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-112.46305600605285,
83.99490356445312
]
]
},
{
"type": "rectangle",
"version": 87,
"versionNonce": 391496953,
"isDeleted": false,
"id": "C4et8o20fl-WIeItQxXWv",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -278.0358886718743,
"y": 1143.337972005208,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 259,
"height": 46,
"seed": 128160633,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "ZQrhA2Ocj9FxFiZde4ien"
},
{
"id": "SJolbrAOGVVcoqvsFrJTh",
"type": "arrow"
},
{
"id": "fcXX9mod4agtBjd_Iuyc_",
"type": "arrow"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 92,
"versionNonce": 431417839,
"isDeleted": false,
"id": "ZQrhA2Ocj9FxFiZde4ien",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -252.42578887939385,
"y": 1153.837972005208,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 207.77980041503906,
"height": 25,
"seed": 1469464633,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214161,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMEmbeddedGeneric",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "C4et8o20fl-WIeItQxXWv",
"originalText": "ODMEmbeddedGeneric",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 25,
"versionNonce": 1176813017,
"isDeleted": false,
"id": "SJolbrAOGVVcoqvsFrJTh",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -280.77868652343693,
"y": 1057.3061726888018,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 135.81829833984375,
"height": 81.1202392578125,
"seed": 1660302935,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": {
"elementId": "MmjpQNm8e4X6meZXbUBEd",
"focus": 0.044913994802893424,
"gap": 1
},
"endBinding": {
"elementId": "C4et8o20fl-WIeItQxXWv",
"focus": 0.29943344568626623,
"gap": 4.91156005859375
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
135.81829833984375,
81.1202392578125
]
]
},
{
"type": "arrow",
"version": 27,
"versionNonce": 703686711,
"isDeleted": false,
"id": "2pkhcWk_2M8N6sAuVG7xG",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -187.88470458984318,
"y": 924.8297627766924,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 120.21661376953125,
"height": 88.65542602539062,
"seed": 372095223,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": {
"elementId": "MmjpQNm8e4X6meZXbUBEd",
"focus": -0.20668517357201982,
"gap": 1
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-120.21661376953125,
88.65542602539062
]
]
},
{
"type": "rectangle",
"version": 44,
"versionNonce": 912366265,
"isDeleted": false,
"id": "xeMCiUJi6USMy-nqhFh5k",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -553.8645757039391,
"y": 1436.3778737386065,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 213.9385986328124,
"height": 60.13557434082031,
"seed": 1889425719,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "hKH0bIyPDlGaYVhvpA8MU"
},
{
"id": "SxuOejZ7cQ3UNmtI49Sb2",
"type": "arrow"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 37,
"versionNonce": 821571713,
"isDeleted": false,
"id": "hKH0bIyPDlGaYVhvpA8MU",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -468.6152547200524,
"y": 1453.9456609090166,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 43.43995666503906,
"height": 25,
"seed": 916853721,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214161,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Field",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "xeMCiUJi6USMy-nqhFh5k",
"originalText": "Field",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 197,
"versionNonce": 1219484569,
"isDeleted": false,
"id": "SxuOejZ7cQ3UNmtI49Sb2",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -336.97790273030637,
"y": 1471.9465178113544,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 311.9809341430663,
"height": 0.05721304216694989,
"seed": 872734553,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [
{
"type": "text",
"id": "S3vTGyK2dRYevYL46pK6K"
}
],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": {
"elementId": "xeMCiUJi6USMy-nqhFh5k",
"focus": 0.18215927101474746,
"gap": 2.9480743408203125
},
"endBinding": {
"elementId": "Vey6HwdwlxRCf1-ELdHDM",
"focus": -0.1492435186961035,
"gap": 12.560348510742188
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "dot",
"points": [
[
0,
0
],
[
311.9809341430663,
0.05721304216694989
]
]
},
{
"type": "text",
"version": 82,
"versionNonce": 507630607,
"isDeleted": false,
"id": "S3vTGyK2dRYevYL46pK6K",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -264.2173322041834,
"y": 1446.9751243324379,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 166.4597930908203,
"height": 50,
"seed": 1636140633,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214161,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "summarizes data\ninto",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "SxuOejZ7cQ3UNmtI49Sb2",
"originalText": "summarizes data\ninto",
"lineHeight": 1.25,
"baseline": 44
},
{
"type": "rectangle",
"version": 93,
"versionNonce": 2101543033,
"isDeleted": false,
"id": "Vey6HwdwlxRCf1-ELdHDM",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -12.436620076497888,
"y": 1428.6966756184893,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 282.4995422363281,
"height": 75.40855407714844,
"seed": 1731901367,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"id": "SxuOejZ7cQ3UNmtI49Sb2",
"type": "arrow"
},
{
"type": "text",
"id": "zkcJBrBFG6KjtKThh-HTM"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 77,
"versionNonce": 1048606817,
"isDeleted": false,
"id": "zkcJBrBFG6KjtKThh-HTM",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 63.85320536295524,
"y": 1453.9009526570635,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 129.91989135742188,
"height": 25,
"seed": 1881440503,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214162,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "ODMFieldInfo",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "Vey6HwdwlxRCf1-ELdHDM",
"originalText": "ODMFieldInfo",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 112,
"versionNonce": 2016154807,
"isDeleted": false,
"id": "_U4VAH79hZax3caf1zOq8",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -655.3383115132654,
"y": 94.75302632649743,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 174.24031066894526,
"height": 36.986572265625,
"seed": 1982922103,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "cW_x4fvTzGufKvISlWlw5"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 111,
"versionNonce": 1110080047,
"isDeleted": false,
"id": "cW_x4fvTzGufKvISlWlw5",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -643.9980710347497,
"y": 100.74631245930993,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 151.55982971191406,
"height": 25,
"seed": 1718751831,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214162,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Pydantic Entity",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "_U4VAH79hZax3caf1zOq8",
"originalText": "Pydantic Entity",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 141,
"versionNonce": 156657111,
"isDeleted": false,
"id": "otL0ZS4UxZlv7n_BeDKXA",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -655.9780743916833,
"y": 153.90234883626306,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 174.24031066894526,
"height": 36.986572265625,
"seed": 1982922103,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "2zopLd99vM4wx7PtlkSZT"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 161,
"versionNonce": 661963841,
"isDeleted": false,
"id": "2zopLd99vM4wx7PtlkSZT",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -642.9578412373864,
"y": 159.89563496907556,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 148.19984436035156,
"height": 25,
"seed": 1718751831,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214162,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Internal Entity",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "otL0ZS4UxZlv7n_BeDKXA",
"originalText": "Internal Entity",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 181,
"versionNonce": 1599705847,
"isDeleted": false,
"id": "Y0HURbIdJHF_oEtdDzMYw",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -655.1873448689782,
"y": 216.05154927571618,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 174.24031066894526,
"height": 36.986572265625,
"seed": 1982922103,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "FujR1BobRApolhMQALX7D"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 218,
"versionNonce": 1247370319,
"isDeleted": false,
"id": "FujR1BobRApolhMQALX7D",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -646.2070973714196,
"y": 222.04483540852868,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 156.27981567382812,
"height": 25,
"seed": 1718751831,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214162,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "End-User Entity",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "Y0HURbIdJHF_oEtdDzMYw",
"originalText": "End-User Entity",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "rectangle",
"version": 58,
"versionNonce": 311858393,
"isDeleted": false,
"id": "HC2a-ym2pU-oHQIc_995A",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 114.74866739908805,
"y": 1294.6465733846023,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 599.5997619628905,
"height": 90.76152801513672,
"seed": 793055897,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"id": "rypSwXtCbrn6iaxCvcrsP",
"type": "arrow"
},
{
"id": "RA_WqOlwJvd3VGyVXeGOC",
"type": "arrow"
},
{
"id": "ZrKEOJwFCNqKazB6CJahM",
"type": "arrow"
},
{
"id": "fcXX9mod4agtBjd_Iuyc_",
"type": "arrow"
},
{
"type": "text",
"id": "-tWSKWdsNztSLCWmcpzs9"
}
],
"updated": 1688229366350,
"link": null,
"locked": false
},
{
"type": "text",
"version": 32,
"versionNonce": 1828852289,
"isDeleted": false,
"id": "-tWSKWdsNztSLCWmcpzs9",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 133.74883524576768,
"y": 1315.0273373921707,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 561.5994262695312,
"height": 50,
"seed": 910294329,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305688067,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": " BaseModelMetaclass::__validate_cls_namespace__\n(infers field objects)",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "HC2a-ym2pU-oHQIc_995A",
"originalText": " BaseModelMetaclass::__validate_cls_namespace__\n(infers field objects)",
"lineHeight": 1.25,
"baseline": 44
},
{
"type": "arrow",
"version": 25,
"versionNonce": 1686147513,
"isDeleted": false,
"id": "wiD99dH8WL9B1QDTSUX1a",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 267.51554361979106,
"y": 1429.027486165364,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 68.04290771484375,
"height": 44.547119140625,
"seed": 2027938711,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688229366350,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "dot",
"points": [
[
0,
0
],
[
68.04290771484375,
-44.547119140625
]
]
},
{
"type": "arrow",
"version": 32,
"versionNonce": 127087681,
"isDeleted": false,
"id": "rypSwXtCbrn6iaxCvcrsP",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 228.8693796793615,
"y": 1293.1112721761062,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 83.17924499511719,
"height": 271.5169525146483,
"seed": 1019883257,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305670853,
"link": null,
"locked": false,
"startBinding": {
"elementId": "HC2a-ym2pU-oHQIc_995A",
"focus": -0.5460796337634443,
"gap": 1.5353012084960938
},
"endBinding": {
"elementId": "gIeBiC2fqLAh_jzsGEknB",
"focus": -0.01584678894333607,
"gap": 7.054367065429346
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "dot",
"points": [
[
0,
0
],
[
-83.17924499511719,
-271.5169525146483
]
]
},
{
"type": "arrow",
"version": 41,
"versionNonce": 1436150863,
"isDeleted": false,
"id": "RA_WqOlwJvd3VGyVXeGOC",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 143.93819681803336,
"y": 1292.188229878743,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 352.2896575927733,
"height": 260.1011276245115,
"seed": 999753753,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305670853,
"link": null,
"locked": false,
"startBinding": {
"elementId": "HC2a-ym2pU-oHQIc_995A",
"focus": -0.719704607309612,
"gap": 2.458343505859375
},
"endBinding": {
"elementId": "MmjpQNm8e4X6meZXbUBEd",
"focus": -0.7049819035073658,
"gap": 3.5337778727204068
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "dot",
"points": [
[
0,
0
],
[
-122.5836944580077,
-179.93125915527344
],
[
-352.2896575927733,
-260.1011276245115
]
]
},
{
"type": "arrow",
"version": 46,
"versionNonce": 1518666785,
"isDeleted": false,
"id": "ZrKEOJwFCNqKazB6CJahM",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 112.30916849772086,
"y": 1346.9785359700516,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 553.0274963378904,
"height": 152.72441864013672,
"seed": 129523831,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305670853,
"link": null,
"locked": false,
"startBinding": {
"elementId": "HC2a-ym2pU-oHQIc_995A",
"focus": -0.49260927353775746,
"gap": 2.4394989013672443
},
"endBinding": {
"elementId": "oBN5q2AVj21FpaTsmD9Up",
"focus": 0.2775831032462999,
"gap": 9.286567687988054
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "dot",
"points": [
[
0,
0
],
[
-304.026641845703,
-30.3009033203125
],
[
-553.0274963378904,
-152.72441864013672
]
]
},
{
"type": "arrow",
"version": 26,
"versionNonce": 1062570607,
"isDeleted": false,
"id": "fcXX9mod4agtBjd_Iuyc_",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 114.43784586588492,
"y": 1317.6760152180984,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 241.41380310058582,
"height": 122.27279663085938,
"seed": 1558695511,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305670853,
"link": null,
"locked": false,
"startBinding": {
"elementId": "HC2a-ym2pU-oHQIc_995A",
"focus": -0.6573732928210425,
"gap": 1
},
"endBinding": {
"elementId": "C4et8o20fl-WIeItQxXWv",
"focus": 0.20482491354805857,
"gap": 6.065246582031023
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "dot",
"points": [
[
0,
0
],
[
-241.41380310058582,
-122.27279663085938
]
]
},
{
"type": "text",
"version": 58,
"versionNonce": 1214171084,
"isDeleted": false,
"id": "pHfVl0dYIJKDYcrEOMmK9",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -485.4066416422527,
"y": 860.0546188354491,
"strokeColor": "#c92a2a",
"backgroundColor": "transparent",
"width": 206.75975036621094,
"height": 25,
"seed": 1730994265,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1702173464927,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "This could be a mixin",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "This could be a mixin",
"lineHeight": 1.25,
"baseline": 17
},
{
"type": "rectangle",
"version": 40,
"versionNonce": 1638334889,
"isDeleted": false,
"id": "3p0_zCTdqx9_AMcvHeSUP",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -519.8740582531865,
"y": 411.80322863749427,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 258.247779712862,
"height": 50.58210925754861,
"seed": 12607113,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "Womk_dRN2udgdNiALINZ2"
},
{
"id": "Jbcd5LEPNRJ78cK3nbitw",
"type": "arrow"
}
],
"updated": 1688231916155,
"link": null,
"locked": false
},
{
"type": "text",
"version": 15,
"versionNonce": 1395615745,
"isDeleted": false,
"id": "Womk_dRN2udgdNiALINZ2",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -469.6300969856227,
"y": 424.5942832662686,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 157.75985717773438,
"height": 25,
"seed": 570760999,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305214163,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "BaseBSONModel",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "3p0_zCTdqx9_AMcvHeSUP",
"originalText": "BaseBSONModel",
"lineHeight": 1.25,
"baseline": 19
},
{
"type": "arrow",
"version": 113,
"versionNonce": 261414049,
"isDeleted": false,
"id": "Jbcd5LEPNRJ78cK3nbitw",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": -249.49425392563194,
"y": 106.93597491259342,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 136.5045325568691,
"height": 296.55682973278925,
"seed": 174030313,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305650906,
"link": null,
"locked": false,
"startBinding": {
"elementId": "W9rThSS1FY0WVyk6rE3OQ",
"focus": 0.737708089535725,
"gap": 14.002427731958903
},
"endBinding": {
"elementId": "3p0_zCTdqx9_AMcvHeSUP",
"focus": 0.010760624764986902,
"gap": 8.310423992111623
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-115.68117810338481,
86.74790906124068
],
[
-136.5045325568691,
296.55682973278925
]
]
},
{
"type": "ellipse",
"version": 95,
"versionNonce": 1689658415,
"isDeleted": false,
"id": "xEhH0_wN5jyLmPzyWIudj",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1518.3688078791301,
"y": 646.3968910086662,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 287,
"height": 146,
"seed": 103681761,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [
{
"type": "text",
"id": "rYeofwk_nNG7xyGB5eiHG"
},
{
"id": "ffnkcB8EFZuallwlVdtdd",
"type": "arrow"
}
],
"updated": 1688305468405,
"link": null,
"locked": false
},
{
"type": "text",
"version": 78,
"versionNonce": 72399425,
"isDeleted": false,
"id": "rYeofwk_nNG7xyGB5eiHG",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1584.4449518809113,
"y": 674.2780959820483,
"strokeColor": "#5c940d",
"backgroundColor": "transparent",
"width": 154.90806579589844,
"height": 90,
"seed": 1167370401,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305468406,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "Model\nInstance",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "xEhH0_wN5jyLmPzyWIudj",
"originalText": "Model\nInstance",
"lineHeight": 1.25,
"baseline": 79
},
{
"type": "rectangle",
"version": 118,
"versionNonce": 695283727,
"isDeleted": false,
"id": "nMQA0fDC9bnsU9zPCt8-T",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2765.250420021056,
"y": 560.7097979097082,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 306.9653320312502,
"height": 124.60088094075513,
"seed": 846558113,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "jbVsjGGw1mU8bnb6LUnSc"
},
{
"id": "AxaE7cUZ-BSE0VL7MjRoi",
"type": "arrow"
}
],
"updated": 1688305439128,
"link": null,
"locked": false
},
{
"type": "text",
"version": 112,
"versionNonce": 150318849,
"isDeleted": false,
"id": "jbVsjGGw1mU8bnb6LUnSc",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2826.8970569839466,
"y": 578.0102383800858,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 183.67205810546875,
"height": 90,
"seed": 1658134689,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305455527,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "BSON\n(MongoDB)",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "nMQA0fDC9bnsU9zPCt8-T",
"originalText": "BSON\n(MongoDB)",
"lineHeight": 1.25,
"baseline": 79
},
{
"type": "rectangle",
"version": 111,
"versionNonce": 1288279297,
"isDeleted": false,
"id": "Q3dEKBCmujX_Ay8m3DKMn",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2777.9644499689725,
"y": 791.0046740083404,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 306.9653320312502,
"height": 124.60088094075513,
"seed": 846558113,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "vi3-gvUrw5do2_kJvWlA-"
},
{
"id": "fwvLwAUKgx8-QOkA0R5n2",
"type": "arrow"
}
],
"updated": 1688305442543,
"link": null,
"locked": false
},
{
"type": "text",
"version": 96,
"versionNonce": 1845950351,
"isDeleted": false,
"id": "vi3-gvUrw5do2_kJvWlA-",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2884.431094256082,
"y": 830.805114478718,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 94.03204345703125,
"height": 45,
"seed": 1658134689,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305442543,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "JSON",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "Q3dEKBCmujX_Ay8m3DKMn",
"originalText": "JSON",
"lineHeight": 1.25,
"baseline": 34
},
{
"type": "rectangle",
"version": 130,
"versionNonce": 77186127,
"isDeleted": false,
"id": "McMQd13P3SUAEnbjRKP8H",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2771.457695411682,
"y": 1028.883397152872,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 306.9653320312502,
"height": 124.60088094075513,
"seed": 846558113,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "1YVzBghuiPwjZE1f6Kf6H"
},
{
"id": "aWk9xp3PGOP38CdG5mCOC",
"type": "arrow"
}
],
"updated": 1688305444498,
"link": null,
"locked": false
},
{
"type": "text",
"version": 122,
"versionNonce": 2114890785,
"isDeleted": false,
"id": "1YVzBghuiPwjZE1f6Kf6H",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2867.1603397598265,
"y": 1068.6838376232495,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 115.56004333496094,
"height": 45,
"seed": 1658134689,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305444498,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "Python",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "McMQd13P3SUAEnbjRKP8H",
"originalText": "Python",
"lineHeight": 1.25,
"baseline": 34
},
{
"type": "diamond",
"version": 76,
"versionNonce": 1443225089,
"isDeleted": false,
"id": "_UD33YwPqEc_7U1DlmWQQ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2012.7095264663694,
"y": 519.9193926362709,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 436,
"height": 380,
"seed": 515878159,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [
{
"type": "text",
"id": "5d1hshx3Va0B-EZbB8HMy"
},
{
"id": "aWk9xp3PGOP38CdG5mCOC",
"type": "arrow"
},
{
"id": "fwvLwAUKgx8-QOkA0R5n2",
"type": "arrow"
},
{
"id": "vghGIdWJkjMlizQZuVSW3",
"type": "arrow"
},
{
"id": "ffnkcB8EFZuallwlVdtdd",
"type": "arrow"
}
],
"updated": 1688305472693,
"link": null,
"locked": false
},
{
"type": "text",
"version": 84,
"versionNonce": 1548729999,
"isDeleted": false,
"id": "5d1hshx3Va0B-EZbB8HMy",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2145.9834980239866,
"y": 664.9193926362709,
"strokeColor": "#a61e4d",
"backgroundColor": "transparent",
"width": 169.45205688476562,
"height": 90,
"seed": 474636737,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305472693,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "Pydantic\nValidation",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "_UD33YwPqEc_7U1DlmWQQ",
"originalText": "Pydantic\nValidation",
"lineHeight": 1.25,
"baseline": 79
},
{
"type": "arrow",
"version": 68,
"versionNonce": 109477889,
"isDeleted": false,
"id": "aWk9xp3PGOP38CdG5mCOC",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2764.1829151382435,
"y": 1116.1441208622223,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 409.3854466414123,
"height": 313.4186826535429,
"seed": 1716494895,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305444498,
"link": null,
"locked": false,
"startBinding": {
"elementId": "McMQd13P3SUAEnbjRKP8H",
"focus": -0.8242044715896969,
"gap": 7.274780273438182
},
"endBinding": {
"elementId": "_UD33YwPqEc_7U1DlmWQQ",
"focus": -0.011545353035105272,
"gap": 8.259361764991667
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-409.3854466414123,
-313.4186826535429
]
]
},
{
"type": "arrow",
"version": 79,
"versionNonce": 880749999,
"isDeleted": false,
"id": "fwvLwAUKgx8-QOkA0R5n2",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2770.499850359599,
"y": 879.076811385773,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 324.8976003957414,
"height": 133.87041983658412,
"seed": 1831724079,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305442543,
"link": null,
"locked": false,
"startBinding": {
"elementId": "Q3dEKBCmujX_Ay8m3DKMn",
"focus": -0.733284930728151,
"gap": 7.4645996093740905
},
"endBinding": {
"elementId": "_UD33YwPqEc_7U1DlmWQQ",
"focus": -0.28030075065247295,
"gap": 24.55988922760585
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-324.8976003957414,
-133.87041983658412
]
]
},
{
"type": "diamond",
"version": 111,
"versionNonce": 1792673185,
"isDeleted": false,
"id": "YbNRk88JD0E1IexXU5XNj",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1843.6774423192337,
"y": 202.8566586681718,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 779,
"height": 132,
"seed": 173672705,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [
{
"type": "text",
"id": "lpwsZx0siAulVGMklvNWZ"
},
{
"id": "AxaE7cUZ-BSE0VL7MjRoi",
"type": "arrow"
},
{
"id": "vghGIdWJkjMlizQZuVSW3",
"type": "arrow"
}
],
"updated": 1688305429987,
"link": null,
"locked": false
},
{
"type": "text",
"version": 77,
"versionNonce": 1273716975,
"isDeleted": false,
"id": "lpwsZx0siAulVGMklvNWZ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2051.3034188817337,
"y": 246.3566586681718,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 364.248046875,
"height": 45,
"seed": 243339599,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1688305429987,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "_parse_doc_to_obj",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "YbNRk88JD0E1IexXU5XNj",
"originalText": "_parse_doc_to_obj",
"lineHeight": 1.25,
"baseline": 34
},
{
"type": "arrow",
"version": 209,
"versionNonce": 407676993,
"isDeleted": false,
"id": "AxaE7cUZ-BSE0VL7MjRoi",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2874.983367637454,
"y": 538.8150632091881,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 262.0004136275752,
"height": 263.3008043478154,
"seed": 1922409967,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305439129,
"link": null,
"locked": false,
"startBinding": {
"elementId": "nMQA0fDC9bnsU9zPCt8-T",
"focus": 0.18565410250667447,
"gap": 21.894734700520075
},
"endBinding": {
"elementId": "YbNRk88JD0E1IexXU5XNj",
"focus": -0.9581021619285586,
"gap": 4.9444073183017
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-262.0004136275752,
-263.3008043478154
]
]
},
{
"type": "arrow",
"version": 87,
"versionNonce": 509528865,
"isDeleted": false,
"id": "vghGIdWJkjMlizQZuVSW3",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2238.1248068919213,
"y": 344.9067463195938,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 9.174741786770483,
"height": 173.8716567373507,
"seed": 1000023841,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305435428,
"link": null,
"locked": false,
"startBinding": {
"elementId": "YbNRk88JD0E1IexXU5XNj",
"focus": -0.0230046831190309,
"gap": 10.735378960051221
},
"endBinding": {
"elementId": "_UD33YwPqEc_7U1DlmWQQ",
"focus": -0.054336970658854254,
"gap": 2.016173751099984
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-9.174741786770483,
173.8716567373507
]
]
},
{
"type": "arrow",
"version": 23,
"versionNonce": 1428368993,
"isDeleted": false,
"id": "ffnkcB8EFZuallwlVdtdd",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 2009.864534944123,
"y": 710.0742224254997,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 190.68023950455336,
"height": 7.194336649818297,
"seed": 1128239631,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"boundElements": [],
"updated": 1688305466084,
"link": null,
"locked": false,
"startBinding": {
"elementId": "_UD33YwPqEc_7U1DlmWQQ",
"focus": 0.04637739376511212,
"gap": 2.8492014715156415
},
"endBinding": {
"elementId": "xEhH0_wN5jyLmPzyWIudj",
"focus": 0.052009991817207474,
"gap": 13.862731797900523
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-190.68023950455336,
7.194336649818297
]
]
},
{
"type": "text",
"version": 102,
"versionNonce": 128674548,
"isDeleted": false,
"id": "7XJlibFb5OHbfd8pUVf83",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1951.1055225601194,
"y": 154.03484063431756,
"strokeColor": "#364fc7",
"backgroundColor": "transparent",
"width": 968.7963256835938,
"height": 45,
"seed": 873796033,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1702173464928,
"link": null,
"locked": false,
"fontSize": 36,
"fontFamily": 1,
"text": "validate ODM schema (references, embedded models,...)",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "validate ODM schema (references, embedded models,...)",
"lineHeight": 1.25,
"baseline": 31
}
],
"appState": {
"gridSize": null,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}
art049-odmantic-07c27e3/docs/img/usage_fastapi_swagger.png 0000664 0000000 0000000 00000223237 15135204315 0023441 0 ustar 00root root 0000000 0000000 PNG
IHDR DN IDATx_pu'ح˽ح˽VrDk-PQ٨PM Z`b44C+(Hcj0*YDPE $!"N''<^9r} HO& A $B $B $B $B $B $B $B $B $B $B $B $B $B $B $B $B $B $B $B c_~ QUy7Ɩ7Oı/xKET{4jՔκڟ[
3=[*n ti;>x)SB|Nv'Q)!>9\t'v `mʆt=<ӾgZ
ʇ3{ 0LSo7Lhݤ7;~ !ÍMz!p늇u7S H L TL>K-i3E Hv\yik\}Z+ 0L#5{_ϱ?⻎qRPTl1>PvGCWt{Ã?O{;ts]]L\Z' ە|g9Ws̳~۾9{ؗDY|8 ]q˟/,soAZ;>3.鋇u?u{ _:Ō/O+u_wȽ8Nv+祠pv,^