pax_global_header 0000666 0000000 0000000 00000000064 14613034133 0014510 g ustar 00root root 0000000 0000000 52 comment=6095d9dc710a8901a4e0b7be92f59486576a2c81
art049-odmantic-6095d9d/ 0000775 0000000 0000000 00000000000 14613034133 0014675 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.codecov.yml 0000664 0000000 0000000 00000000104 14613034133 0017113 0 ustar 00root root 0000000 0000000 codecov:
require_ci_to_pass: yes
notify:
after_n_builds: 14
art049-odmantic-6095d9d/.darglint 0000664 0000000 0000000 00000000144 14613034133 0016501 0 ustar 00root root 0000000 0000000 [darglint]
# Allow one line docstrings without arg spec
strictness = short
docstring_style = google
art049-odmantic-6095d9d/.devcontainer/ 0000775 0000000 0000000 00000000000 14613034133 0017434 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.devcontainer/Dockerfile 0000664 0000000 0000000 00000001017 14613034133 0021425 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-6095d9d/.devcontainer/devcontainer.json 0000664 0000000 0000000 00000002057 14613034133 0023014 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-6095d9d/.devcontainer/docker-compose.yml 0000664 0000000 0000000 00000001003 14613034133 0023063 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-6095d9d/.gitattributes 0000664 0000000 0000000 00000000052 14613034133 0017565 0 ustar 00root root 0000000 0000000 *.png filter=lfs diff=lfs merge=lfs -text
art049-odmantic-6095d9d/.github/ 0000775 0000000 0000000 00000000000 14613034133 0016235 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14613034133 0020420 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.github/ISSUE_TEMPLATE/bug.md 0000664 0000000 0000000 00000001137 14613034133 0021521 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-6095d9d/.github/ISSUE_TEMPLATE/feature.md 0000664 0000000 0000000 00000001226 14613034133 0022376 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-6095d9d/.github/Taskfile.yml 0000664 0000000 0000000 00000002255 14613034133 0020526 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 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:
- python .github/release.py
publish-release:
vars:
RELEASE_NOTE:
sh: cat {{.RELEASE_NOTE_FILE}}
NEW_VERSION:
sh: cat {{.VERSION_FILE}}
cmds:
- 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-6095d9d/.github/dependabot.yml 0000664 0000000 0000000 00000000307 14613034133 0021065 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "⬆️"
art049-odmantic-6095d9d/.github/latest-changes.jinja2 0000664 0000000 0000000 00000000143 14613034133 0022234 0 ustar 00root root 0000000 0000000 - {{pr.title}} ([#{{pr.number}}]({{pr.html_url}}) by [@{{pr.user.login}}]({{pr.user.html_url}}))
art049-odmantic-6095d9d/.github/release.py 0000664 0000000 0000000 00000011523 14613034133 0020231 0 ustar 00root root 0000000 0000000 import datetime
import importlib.metadata
import os
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:
version = importlib.metadata.version("odmantic")
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,
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-6095d9d/.github/workflows/ 0000775 0000000 0000000 00000000000 14613034133 0020272 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.github/workflows/ci.yml 0000664 0000000 0000000 00000013172 14613034133 0021414 0 ustar 00root root 0000000 0000000 name: build
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: "0 2 * * *"
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.8"
- uses: pre-commit/action@v2.0.0
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" # FIXME: async-asgi-testclient doesn't support Python 3.12 yet
pydantic-version:
- "2.5.2"
motor-version:
- "3.1.1"
- "3.2.0"
- "3.3.2"
steps:
- uses: actions/checkout@v4
- name: Mongo Service
id: mongo-service
uses: art049/mongodb-cluster-action@v0
with:
version: "4.4"
mode: standalone
- name: "Set up Python ${{ matrix.python-version }}"
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
- name: Cache environment
uses: actions/cache@v2
id: cache
with:
path: ${{ env.pythonLocation }}
key: env-compatibility-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.pydantic-version }}-${{ matrix.motor-version }}-${{ hashFiles('pyproject.toml') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: |
pip install ".[test]"
pip install "pydantic==${{ matrix.pydantic-version }}" "motor==${{ matrix.motor-version }}"
- name: Run compatibility checks.
run: |
python -c "import motor; print(motor.version)" 1>&2
python -c "import pydantic; print(pydantic.VERSION)" 1>&2
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"
mongo-version:
- "4.4"
- "5"
- "6"
mongo-mode:
- standalone
include:
- python-version: 3.11
mongo-version: 4.0
mongo-mode: replicaSet
- python-version: 3.11
mongo-version: 4.2
mongo-mode: sharded
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: "pyproject.toml"
- name: Mongo Service
id: mongo-service
uses: art049/mongodb-cluster-action@v0
with:
version: ${{ matrix.mongo-version }}
mode: ${{ matrix.mongo-mode }}
- name: Install dependencies
run: |
pip install flit
pip install ".[test]"
- name: Run all tests
run: |
set -e
coverage run -m pytest -v
coverage report -m
coverage xml
env:
TEST_MONGO_URI: ${{ steps.mongo-service.outputs.connection-string }}
TEST_MONGO_MODE: ${{ matrix.mongo-mode }}
- uses: codecov/codecov-action@v3
if: github.event_name != 'schedule' # Don't report coverage for nightly builds
with:
file: ./coverage.xml
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
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
path: odmantic-current
- uses: actions/checkout@v4
with:
repository: art049/fastapi-odmantic-realworld-example
submodules: recursive
path: fastapi-odmantic-realworld-example
- name: Install poetry and flit
run: |
pipx install poetry
pipx install flit
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "poetry"
- name: Install dependencies (w/o ODMantic)
working-directory: fastapi-odmantic-realworld-example
run: |
echo "$(grep -v 'odmantic =' ./pyproject.toml)" > pyproject.toml
poetry lock --no-update
poetry install
- name: Install current ODMantic version
working-directory: fastapi-odmantic-realworld-example
run: poetry run pip install ../odmantic-current/
- name: Start the MongoDB instance
uses: art049/mongodb-cluster-action@v0
id: mongodb-cluster-action
- 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: ${{ steps.mongodb-cluster-action.outputs.connection-string }}
- 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-6095d9d/.github/workflows/codspeed.yml 0000664 0000000 0000000 00000002122 14613034133 0022600 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:
jobs:
benchmarks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.12
cache: pip
cache-dependency-path: "pyproject.toml"
- name: Mongo Service
id: mongo-service
uses: art049/mongodb-cluster-action@v0
with:
version: "4.2"
mode: "sharded"
- name: Install dependencies
run: |
pip install flit
pip install ".[test]"
- name: Run benches
uses: CodSpeedHQ/action@v2
with:
run: pytest tests/integration/benchmarks --codspeed
env:
TEST_MONGO_URI: ${{ steps.mongo-service.outputs.connection-string }}
TEST_MONGO_MODE: "sharded"
art049-odmantic-6095d9d/.github/workflows/docs-preview.yml 0000664 0000000 0000000 00000002302 14613034133 0023421 0 ustar 00root root 0000000 0000000 name: docs-preview
on:
- pull_request
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
cache: pip
cache-dependency-path: "pyproject.toml"
- name: Install dependencies
run: |
pip install flit
pip install ".[doc]"
- name: Build documentation
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-6095d9d/.github/workflows/docs.yml 0000664 0000000 0000000 00000001376 14613034133 0021754 0 ustar 00root root 0000000 0000000 name: docs
on:
release:
types:
- published
- released
- edited
workflow_dispatch:
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
cache: pip
cache-dependency-path: "pyproject.toml"
- name: Install dependencies
run: |
pip install flit
pip install ".[doc]"
- name: Build documentation
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-6095d9d/.github/workflows/latest-changes.yml 0000664 0000000 0000000 00000001320 14613034133 0023713 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@v2
with:
token: ${{ secrets.GH_W_TOKEN }}
- name: Disable LFS hooks
run: rm .git/hooks/post-commit .git/hooks/pre-push
- uses: docker://tiangolo/latest-changes:0.0.3
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-6095d9d/.github/workflows/release.yml 0000664 0000000 0000000 00000001025 14613034133 0022433 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@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
run: pip install flit
- name: Build the package
run: flit build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
art049-odmantic-6095d9d/.gitignore 0000664 0000000 0000000 00000002364 14613034133 0016672 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
art049-odmantic-6095d9d/.gitmodules 0000664 0000000 0000000 00000000202 14613034133 0017044 0 ustar 00root root 0000000 0000000 [submodule ".mongodb-cluster-action"]
path = .mongodb-cluster-action
url = https://github.com/art049/mongodb-cluster-action.git
art049-odmantic-6095d9d/.mongodb-cluster-action/ 0000775 0000000 0000000 00000000000 14613034133 0021332 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.pre-commit-config.yaml 0000664 0000000 0000000 00000002741 14613034133 0021162 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.8
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-6095d9d/.prettierignore 0000664 0000000 0000000 00000000032 14613034133 0017733 0 ustar 00root root 0000000 0000000 docs/**/*.md
CHANGELOG.md
art049-odmantic-6095d9d/.vscode/ 0000775 0000000 0000000 00000000000 14613034133 0016236 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/.vscode/extensions.json 0000664 0000000 0000000 00000000412 14613034133 0021325 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-6095d9d/.vscode/launch.json 0000664 0000000 0000000 00000001054 14613034133 0020403 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-6095d9d/.vscode/settings.json 0000664 0000000 0000000 00000000764 14613034133 0021000 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-6095d9d/CHANGELOG.md 0000664 0000000 0000000 00000052757 14613034133 0016526 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.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
[unreleased]: https://github.com/art049/odmantic/compare/v1.0.2...HEAD
art049-odmantic-6095d9d/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000012147 14613034133 0017501 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-6095d9d/CONTRIBUTING.md 0000664 0000000 0000000 00000000165 14613034133 0017130 0 ustar 00root root 0000000 0000000 Please see the [contributing guidelines](https://art049.github.io/odmantic/contributing/) on the documentation site.
art049-odmantic-6095d9d/LICENSE 0000664 0000000 0000000 00000001351 14613034133 0015702 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-6095d9d/README.md 0000664 0000000 0000000 00000023334 14613034133 0016161 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-6095d9d/SECURITY.md 0000664 0000000 0000000 00000001371 14613034133 0016470 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-6095d9d/Taskfile.yml 0000664 0000000 0000000 00000004671 14613034133 0017172 0 ustar 00root root 0000000 0000000 # https://taskfile.dev
version: "3"
silent: true
includes:
release: ./.github
mongodb:
taskfile: ./.mongodb-cluster-action/Taskfile.yml
dir: .mongodb-cluster-action
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:
- python -m pytest -rs -n auto
bench:
desc: |
Run the benches with the current version.
deps:
- task: "mongodb:check"
cmds:
- python -m pytest --benchmark-enable --benchmark-only
default:
desc: |
Run the tests related to changes with the current version.
deps:
- task: mongodb
cmds:
- 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:
- python -m isort odmantic tests
- python -m black 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:flit
cmds:
- flit install --deps=all --python python
sources:
- pyproject.toml
setup:flit:
cmds:
- pip install flit
status:
- which flit
clean:
cmds:
- rm -rf dist/
- rm -rf htmlcov/ ./.coverage ./coverage.xml
- rm -rf .task/ ./__release_notes__.md ./__version__.txt
art049-odmantic-6095d9d/dependabot.yml 0000664 0000000 0000000 00000000150 14613034133 0017521 0 ustar 00root root 0000000 0000000 version: 2
groups:
dev-dependencies:
dependency-type: development
applies-to: version-updates
art049-odmantic-6095d9d/docs/ 0000775 0000000 0000000 00000000000 14613034133 0015625 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0017732 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/api_reference/ 0000775 0000000 0000000 00000000000 14613034133 0020414 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/api_reference/bson.md 0000664 0000000 0000000 00000001644 14613034133 0021704 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-6095d9d/docs/api_reference/config.md 0000664 0000000 0000000 00000000042 14613034133 0022177 0 ustar 00root root 0000000 0000000 ::: odmantic.config.ODMConfigDict
art049-odmantic-6095d9d/docs/api_reference/engine.md 0000664 0000000 0000000 00000000175 14613034133 0022206 0 ustar 00root root 0000000 0000000 ::: odmantic.engine.AIOEngine
::: odmantic.engine.AIOCursor
::: odmantic.engine.SyncEngine
::: odmantic.engine.SyncCursor
art049-odmantic-6095d9d/docs/api_reference/exceptions.md 0000664 0000000 0000000 00000000414 14613034133 0023116 0 ustar 00root root 0000000 0000000 ::: odmantic.exceptions.BaseEngineException
::: odmantic.exceptions.DocumentNotFoundError
::: odmantic.exceptions.DocumentParsingError
selection:
members:
-
::: odmantic.exceptions.DuplicateKeyError
selection:
members:
-
art049-odmantic-6095d9d/docs/api_reference/field.md 0000664 0000000 0000000 00000000031 14613034133 0022013 0 ustar 00root root 0000000 0000000 ::: odmantic.field.Field
art049-odmantic-6095d9d/docs/api_reference/index.md 0000664 0000000 0000000 00000000031 14613034133 0022037 0 ustar 00root root 0000000 0000000 ::: odmantic.index.Index
art049-odmantic-6095d9d/docs/api_reference/model.md 0000664 0000000 0000000 00000000525 14613034133 0022040 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-6095d9d/docs/api_reference/query.md 0000664 0000000 0000000 00000000720 14613034133 0022102 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-6095d9d/docs/api_reference/reference.md 0000664 0000000 0000000 00000000041 14613034133 0022667 0 ustar 00root root 0000000 0000000 ::: odmantic.reference.Reference
art049-odmantic-6095d9d/docs/api_reference/session.md 0000664 0000000 0000000 00000000325 14613034133 0022421 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-6095d9d/docs/api_reference/templates/ 0000775 0000000 0000000 00000000000 14613034133 0022412 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/api_reference/templates/python/ 0000775 0000000 0000000 00000000000 14613034133 0023733 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/api_reference/templates/python/material/ 0000775 0000000 0000000 00000000000 14613034133 0025531 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/api_reference/templates/python/material/properties.html 0000664 0000000 0000000 00000000430 14613034133 0030610 0 ustar 00root root 0000000 0000000 {% if properties %}
{% for property in properties %} {% if property != "pydantic-model" %}
{{ property }}
{% endif %} {% endfor %}
{% endif %}
art049-odmantic-6095d9d/docs/changelog.md 0000777 0000000 0000000 00000000000 14613034133 0022115 2../CHANGELOG.md ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/contributing.md 0000664 0000000 0000000 00000012540 14613034133 0020660 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-6095d9d/docs/css/ 0000775 0000000 0000000 00000000000 14613034133 0016415 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/css/extra.css 0000664 0000000 0000000 00000000052 14613034133 0020247 0 ustar 00root root 0000000 0000000 code {
--md-code-fg-color: #4cae4fbb;
}
art049-odmantic-6095d9d/docs/engine.md 0000664 0000000 0000000 00000030222 14613034133 0017413 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-6095d9d/docs/examples_src/ 0000775 0000000 0000000 00000000000 14613034133 0020312 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/__init__.py 0000664 0000000 0000000 00000000000 14613034133 0022411 0 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/engine/ 0000775 0000000 0000000 00000000000 14613034133 0021557 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/engine/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0023664 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/engine/async/ 0000775 0000000 0000000 00000000000 14613034133 0022674 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/engine/async/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0025001 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/engine/async/count.py 0000664 0000000 0000000 00000000562 14613034133 0024401 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-6095d9d/docs/examples_src/engine/async/create.py 0000664 0000000 0000000 00000000603 14613034133 0024510 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-6095d9d/docs/examples_src/engine/async/delete.py 0000664 0000000 0000000 00000000315 14613034133 0024507 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-6095d9d/docs/examples_src/engine/async/engine_creation.py 0000664 0000000 0000000 00000000307 14613034133 0026377 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-6095d9d/docs/examples_src/engine/async/fetch_async_for.py 0000664 0000000 0000000 00000000506 14613034133 0026403 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-6095d9d/docs/examples_src/engine/async/fetch_await.py 0000664 0000000 0000000 00000000543 14613034133 0025526 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-6095d9d/docs/examples_src/engine/async/fetch_find_one.py 0000664 0000000 0000000 00000000606 14613034133 0026202 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-6095d9d/docs/examples_src/engine/async/patch_multiple_fields_dict.py 0000664 0000000 0000000 00000001134 14613034133 0030610 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-6095d9d/docs/examples_src/engine/async/patch_multiple_fields_pydantic.py 0000664 0000000 0000000 00000001417 14613034133 0031504 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-6095d9d/docs/examples_src/engine/async/primary_key_update.py 0000664 0000000 0000000 00000001021 14613034133 0027135 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-6095d9d/docs/examples_src/engine/async/remove.py 0000664 0000000 0000000 00000000317 14613034133 0024544 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-6095d9d/docs/examples_src/engine/async/remove_just_one.py 0000664 0000000 0000000 00000000344 14613034133 0026452 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-6095d9d/docs/examples_src/engine/async/save_with_session.py 0000664 0000000 0000000 00000001002 14613034133 0026773 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-6095d9d/docs/examples_src/engine/async/save_with_transaction.py 0000664 0000000 0000000 00000001046 14613034133 0027645 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-6095d9d/docs/examples_src/engine/async/transaction_from_session.py 0000664 0000000 0000000 00000001135 14613034133 0030361 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-6095d9d/docs/examples_src/engine/async/update.py 0000664 0000000 0000000 00000000410 14613034133 0024523 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-6095d9d/docs/examples_src/engine/sync/ 0000775 0000000 0000000 00000000000 14613034133 0022533 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/engine/sync/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0024640 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/engine/sync/count.py 0000664 0000000 0000000 00000000542 14613034133 0024236 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-6095d9d/docs/examples_src/engine/sync/create.py 0000664 0000000 0000000 00000000571 14613034133 0024353 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-6095d9d/docs/examples_src/engine/sync/delete.py 0000664 0000000 0000000 00000000303 14613034133 0024343 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-6095d9d/docs/examples_src/engine/sync/engine_creation.py 0000664 0000000 0000000 00000000257 14613034133 0026242 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-6095d9d/docs/examples_src/engine/sync/fetch_async_for.py 0000664 0000000 0000000 00000000502 14613034133 0026236 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-6095d9d/docs/examples_src/engine/sync/fetch_await.py 0000664 0000000 0000000 00000000545 14613034133 0025367 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-6095d9d/docs/examples_src/engine/sync/fetch_find_one.py 0000664 0000000 0000000 00000000574 14613034133 0026045 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-6095d9d/docs/examples_src/engine/sync/patch_multiple_fields_dict.py 0000664 0000000 0000000 00000001122 14613034133 0030444 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-6095d9d/docs/examples_src/engine/sync/patch_multiple_fields_pydantic.py 0000664 0000000 0000000 00000001405 14613034133 0031340 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-6095d9d/docs/examples_src/engine/sync/primary_key_update.py 0000664 0000000 0000000 00000001001 14613034133 0026772 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-6095d9d/docs/examples_src/engine/sync/remove.py 0000664 0000000 0000000 00000000313 14613034133 0024377 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-6095d9d/docs/examples_src/engine/sync/remove_just_one.py 0000664 0000000 0000000 00000000340 14613034133 0026305 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-6095d9d/docs/examples_src/engine/sync/save_with_session.py 0000664 0000000 0000000 00000000762 14613034133 0026646 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-6095d9d/docs/examples_src/engine/sync/save_with_transaction.py 0000664 0000000 0000000 00000000776 14613034133 0027515 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-6095d9d/docs/examples_src/engine/sync/transaction_from_session.py 0000664 0000000 0000000 00000001057 14613034133 0030223 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-6095d9d/docs/examples_src/engine/sync/update.py 0000664 0000000 0000000 00000000376 14613034133 0024375 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-6095d9d/docs/examples_src/fields/ 0000775 0000000 0000000 00000000000 14613034133 0021560 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/fields/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0023665 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/fields/async/ 0000775 0000000 0000000 00000000000 14613034133 0022675 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/fields/async/custom_key_name.py 0000664 0000000 0000000 00000000255 14613034133 0026433 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-6095d9d/docs/examples_src/fields/async/custom_primary_field.py 0000664 0000000 0000000 00000000374 14613034133 0027473 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-6095d9d/docs/examples_src/fields/async/indexed_field.py 0000664 0000000 0000000 00000000266 14613034133 0026036 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-6095d9d/docs/examples_src/fields/async/unique_field.py 0000664 0000000 0000000 00000000712 14613034133 0025720 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-6095d9d/docs/examples_src/fields/container_dict.py 0000664 0000000 0000000 00000001061 14613034133 0025115 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-6095d9d/docs/examples_src/fields/container_list.py 0000664 0000000 0000000 00000001104 14613034133 0025143 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-6095d9d/docs/examples_src/fields/container_tuple.py 0000664 0000000 0000000 00000001116 14613034133 0025324 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-6095d9d/docs/examples_src/fields/custom_bson_serialization.py 0000664 0000000 0000000 00000002070 14613034133 0027421 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-6095d9d/docs/examples_src/fields/custom_field_validators.py 0000664 0000000 0000000 00000002553 14613034133 0027044 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-6095d9d/docs/examples_src/fields/default_value.py 0000664 0000000 0000000 00000000305 14613034133 0024750 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-6095d9d/docs/examples_src/fields/default_value_field.py 0000664 0000000 0000000 00000000341 14613034133 0026113 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-6095d9d/docs/examples_src/fields/enum.py 0000664 0000000 0000000 00000000756 14613034133 0023106 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-6095d9d/docs/examples_src/fields/inconsistent_enum_1.py 0000664 0000000 0000000 00000000217 14613034133 0026116 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-6095d9d/docs/examples_src/fields/inconsistent_enum_2.py 0000664 0000000 0000000 00000000300 14613034133 0026110 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-6095d9d/docs/examples_src/fields/objectid.py 0000664 0000000 0000000 00000000400 14613034133 0023707 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-6095d9d/docs/examples_src/fields/optional.py 0000664 0000000 0000000 00000000257 14613034133 0023763 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-6095d9d/docs/examples_src/fields/sync/ 0000775 0000000 0000000 00000000000 14613034133 0022534 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/fields/sync/custom_key_name.py 0000664 0000000 0000000 00000000251 14613034133 0026266 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-6095d9d/docs/examples_src/fields/sync/custom_primary_field.py 0000664 0000000 0000000 00000000370 14613034133 0027326 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-6095d9d/docs/examples_src/fields/sync/indexed_field.py 0000664 0000000 0000000 00000000262 14613034133 0025671 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-6095d9d/docs/examples_src/fields/sync/unique_field.py 0000664 0000000 0000000 00000000672 14613034133 0025564 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-6095d9d/docs/examples_src/fields/union.py 0000664 0000000 0000000 00000000360 14613034133 0023261 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-6095d9d/docs/examples_src/fields/validation_field_descriptor.py 0000664 0000000 0000000 00000001015 14613034133 0027662 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-6095d9d/docs/examples_src/fields/validation_strict_types.py 0000664 0000000 0000000 00000000305 14613034133 0027076 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-6095d9d/docs/examples_src/modeling/ 0000775 0000000 0000000 00000000000 14613034133 0022110 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/modeling/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0024215 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/modeling/async/ 0000775 0000000 0000000 00000000000 14613034133 0023225 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/modeling/async/index_creation.py 0000664 0000000 0000000 00000000220 14613034133 0026564 0 ustar 00root root 0000000 0000000 # ... Continuation of the previous snippet ...
from odmantic import AIOEngine
engine = AIOEngine()
await engine.configure_database([Product])
art049-odmantic-6095d9d/docs/examples_src/modeling/compound_index.py 0000664 0000000 0000000 00000000562 14613034133 0025500 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-6095d9d/docs/examples_src/modeling/compound_index_sort_order.py 0000664 0000000 0000000 00000000371 14613034133 0027740 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-6095d9d/docs/examples_src/modeling/custom_text_index.py 0000664 0000000 0000000 00000000436 14613034133 0026232 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-6095d9d/docs/examples_src/modeling/custom_validators.py 0000664 0000000 0000000 00000002675 14613034133 0026236 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-6095d9d/docs/examples_src/modeling/many_to_many.py 0000664 0000000 0000000 00000001136 14613034133 0025155 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-6095d9d/docs/examples_src/modeling/many_to_many_1.py 0000664 0000000 0000000 00000000472 14613034133 0025377 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-6095d9d/docs/examples_src/modeling/many_to_one.py 0000664 0000000 0000000 00000001172 14613034133 0024772 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-6095d9d/docs/examples_src/modeling/one_to_many.py 0000664 0000000 0000000 00000001237 14613034133 0024774 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-6095d9d/docs/examples_src/modeling/one_to_one.py 0000664 0000000 0000000 00000001101 14613034133 0024577 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-6095d9d/docs/examples_src/modeling/one_to_one_1.py 0000664 0000000 0000000 00000000415 14613034133 0025026 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-6095d9d/docs/examples_src/modeling/sync/ 0000775 0000000 0000000 00000000000 14613034133 0023064 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/modeling/sync/index_creation.py 0000664 0000000 0000000 00000000214 14613034133 0026426 0 ustar 00root root 0000000 0000000 # ... Continuation of the previous snippet ...
from odmantic import SyncEngine
engine = SyncEngine()
engine.configure_database([Product])
art049-odmantic-6095d9d/docs/examples_src/querying/ 0000775 0000000 0000000 00000000000 14613034133 0022155 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/querying/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0024262 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/querying/and.py 0000664 0000000 0000000 00000000614 14613034133 0023272 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-6095d9d/docs/examples_src/querying/asc.py 0000664 0000000 0000000 00000000576 14613034133 0023305 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-6095d9d/docs/examples_src/querying/desc.py 0000664 0000000 0000000 00000000521 14613034133 0023443 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-6095d9d/docs/examples_src/querying/embedded.py 0000664 0000000 0000000 00000000640 14613034133 0024260 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-6095d9d/docs/examples_src/querying/embedded_sort.py 0000664 0000000 0000000 00000000517 14613034133 0025332 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-6095d9d/docs/examples_src/querying/enum.py 0000664 0000000 0000000 00000000664 14613034133 0023501 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-6095d9d/docs/examples_src/querying/equal.py 0000664 0000000 0000000 00000000472 14613034133 0023641 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-6095d9d/docs/examples_src/querying/gt_e.py 0000664 0000000 0000000 00000001046 14613034133 0023446 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-6095d9d/docs/examples_src/querying/in.py 0000664 0000000 0000000 00000000436 14613034133 0023140 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-6095d9d/docs/examples_src/querying/lt_e.py 0000664 0000000 0000000 00000001046 14613034133 0023453 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-6095d9d/docs/examples_src/querying/match.py 0000664 0000000 0000000 00000000356 14613034133 0023627 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-6095d9d/docs/examples_src/querying/multiple_sort.py 0000664 0000000 0000000 00000000473 14613034133 0025435 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-6095d9d/docs/examples_src/querying/nor.py 0000664 0000000 0000000 00000000510 14613034133 0023321 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-6095d9d/docs/examples_src/querying/not_equal.py 0000664 0000000 0000000 00000000472 14613034133 0024521 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-6095d9d/docs/examples_src/querying/not_in.py 0000664 0000000 0000000 00000000446 14613034133 0024021 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-6095d9d/docs/examples_src/querying/or.py 0000664 0000000 0000000 00000000607 14613034133 0023152 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-6095d9d/docs/examples_src/raw_query_usage/ 0000775 0000000 0000000 00000000000 14613034133 0023514 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/raw_query_usage/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0025621 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/raw_query_usage/aggregation_example.py 0000664 0000000 0000000 00000002415 14613034133 0030072 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-6095d9d/docs/examples_src/raw_query_usage/collection_name.py 0000664 0000000 0000000 00000000167 14613034133 0027225 0 ustar 00root root 0000000 0000000 from odmantic import Model
class User(Model):
name: str
collection_name = +User
print(collection_name)
#> user
art049-odmantic-6095d9d/docs/examples_src/raw_query_usage/create_from_raw.py 0000664 0000000 0000000 00000000506 14613034133 0027226 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-6095d9d/docs/examples_src/raw_query_usage/extract_from_existing.py 0000664 0000000 0000000 00000000337 14613034133 0030500 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-6095d9d/docs/examples_src/raw_query_usage/field_key_name.py 0000664 0000000 0000000 00000000243 14613034133 0027020 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-6095d9d/docs/examples_src/raw_query_usage/motor_collection.py 0000664 0000000 0000000 00000001121 14613034133 0027434 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-6095d9d/docs/examples_src/raw_query_usage/parse_with_unset_default.py 0000664 0000000 0000000 00000000537 14613034133 0031162 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-6095d9d/docs/examples_src/raw_query_usage/parse_with_unset_default_factory.py 0000664 0000000 0000000 00000001230 14613034133 0032700 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 14613034133 0034277 0 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/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-6095d9d/docs/examples_src/raw_query_usage/pymongo_collection.py 0000664 0000000 0000000 00000000635 14613034133 0027775 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-6095d9d/docs/examples_src/raw_query_usage/raw_query_filters.py 0000664 0000000 0000000 00000000170 14613034133 0027632 0 ustar 00root root 0000000 0000000 from odmantic import AIOEngine, Model
class Tree(Model):
name: str
average_size: float
engine = AIOEngine()
art049-odmantic-6095d9d/docs/examples_src/raw_query_usage/raw_query_filters_1.py 0000664 0000000 0000000 00000000215 14613034133 0030052 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-6095d9d/docs/examples_src/usage_fastapi/ 0000775 0000000 0000000 00000000000 14613034133 0023125 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/usage_fastapi/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0025232 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/usage_fastapi/base_example.py 0000664 0000000 0000000 00000001470 14613034133 0026126 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-6095d9d/docs/examples_src/usage_fastapi/example_delete.py 0000664 0000000 0000000 00000001260 14613034133 0026453 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-6095d9d/docs/examples_src/usage_fastapi/example_update.py 0000664 0000000 0000000 00000001553 14613034133 0026500 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-6095d9d/docs/examples_src/usage_pydantic/ 0000775 0000000 0000000 00000000000 14613034133 0023311 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/examples_src/usage_pydantic/__init__.py 0000664 0000000 0000000 00000000015 14613034133 0025416 0 ustar 00root root 0000000 0000000 # Helps mypy
art049-odmantic-6095d9d/docs/examples_src/usage_pydantic/custom_encoders.py 0000664 0000000 0000000 00000000652 14613034133 0027062 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-6095d9d/docs/fields.md 0000664 0000000 0000000 00000030536 14613034133 0017424 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-6095d9d/docs/img/ 0000775 0000000 0000000 00000000000 14613034133 0016401 5 ustar 00root root 0000000 0000000 art049-odmantic-6095d9d/docs/img/internals.excalidraw 0000664 0000000 0000000 00000310433 14613034133 0022451 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-6095d9d/docs/img/usage_fastapi_swagger.png 0000664 0000000 0000000 00000223237 14613034133 0023452 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,^