Compare commits

..

278 Commits

Author SHA1 Message Date
Sebastián Ramírez
b1c2f822c9 🔖 Release version 0.0.12 2023-11-18 12:32:59 +01:00
Sebastián Ramírez
382b1b0cbb 📝 Update release notes 2023-11-18 12:32:24 +01:00
github-actions
6d00f6fcbd 📝 Update release notes 2023-11-18 11:30:55 +00:00
Sebastián Ramírez
8ed856d322 Upgrade SQLAlchemy to 2.0, including initial work by farahats9 (#700)
Co-authored-by: Mohamed Farahat <farahats9@yahoo.com>
Co-authored-by: Stefan Borer <stefan.borer@gmail.com>
Co-authored-by: Peter Landry <peter.landry@gmail.com>
2023-11-18 12:30:37 +01:00
github-actions
77c6fed305 📝 Update release notes 2023-11-08 16:04:47 +00:00
pre-commit-ci[bot]
c0294423f4 ⬆ [pre-commit.ci] pre-commit autoupdate (#686)
updates:
- https://github.com/charliermarsh/ruff-pre-commithttps://github.com/astral-sh/ruff-pre-commit
- [github.com/astral-sh/ruff-pre-commit: v0.1.2 → v0.1.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.2...v0.1.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-11-08 17:04:24 +01:00
github-actions
e05eae6a49 📝 Update release notes 2023-11-04 01:43:16 +00:00
Sebastián Ramírez
ed22232dee 👷 Upgrade latest-changes GitHub Action (#693) 2023-11-04 01:42:52 +00:00
Sebastián Ramírez
dacc1fa9ca 🔖 Release version 0.0.11 2023-10-29 13:56:39 +04:00
Sebastián Ramírez
0b2d015fc4 📝 Update release notes 2023-10-29 13:56:00 +04:00
github-actions
d8a20d9c21 📝 Update release notes 2023-10-29 09:21:52 +00:00
dependabot[bot]
188f7cd172 ⬆ Update coverage requirement from ^6.2 to >=6.2,<8.0 (#663)
Updates the requirements on [coverage](https://github.com/nedbat/coveragepy) to permit the latest version.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.2...7.2.7)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-29 09:18:26 +00:00
github-actions
12af94655b 📝 Update release notes 2023-10-29 09:09:02 +00:00
dependabot[bot]
4bb99763b1 ⬆ Update mkdocs-material requirement from 9.1.21 to 9.2.7 (#675)
Updates the requirements on [mkdocs-material](https://github.com/squidfunk/mkdocs-material) to permit the latest version.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.21...9.2.7)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-29 13:08:22 +04:00
github-actions
9dd11ef74f 📝 Update release notes 2023-10-29 09:00:31 +00:00
Sebastián Ramírez
fa5d14e413 ⬆️ Upgrade mypy manually (#684) 2023-10-29 08:59:56 +00:00
github-actions
df1efcfa7f 📝 Update release notes 2023-10-29 08:55:43 +00:00
dependabot[bot]
fc1d7afae9 ⬆ Update black requirement from ^22.10.0 to >=22.10,<24.0 (#664)
Updates the requirements on [black](https://github.com/psf/black) to permit the latest version.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.10.0...23.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-29 08:55:04 +00:00
github-actions
a2d1b4eeaf 📝 Update release notes 2023-10-29 08:52:01 +00:00
Sebastián Ramírez
94f3765fcf 👷 Update CI to build MkDocs Insiders only when the secrets are available, for Dependabot (#683) 2023-10-29 12:51:26 +04:00
github-actions
31ed654dd0 📝 Update release notes 2023-10-29 08:11:21 +00:00
Maruo.S
cbaf172c63 Add support for passing a custom SQLAlchemy type to Field() with sa_type (#505)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-29 12:10:39 +04:00
github-actions
c557cf6d18 📝 Update release notes 2023-10-29 07:25:17 +00:00
Matthieu LAURENT
9632980664 🎨 Update inline source examples, hide # in annotations (from MkDocs Material) (#677)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-29 07:24:32 +00:00
github-actions
6457775a0f 📝 Update release notes 2023-10-28 13:55:56 +00:00
Sebastián Ramírez
717594ef13 Do not allow invalid combinations of field parameters for columns and relationships, sa_column excludes sa_column_args, primary_key, nullable, etc. (#681)
* ♻️ Make sa_column exclusive, do not allow incompatible arguments, sa_column_args, primary_key, etc

*  Add tests for new errors when incorrectly using sa_column

*  Add tests for sa_column_args and sa_column_kwargs

* ♻️ Do not allow sa_relationship with sa_relationship_args or sa_relationship_kwargs

*  Add tests for relationship errors

*  Fix test for sa_column_args
2023-10-28 17:55:23 +04:00
Sebastián Ramírez
e4e1385eed 🔖 Release version 0.0.10 2023-10-26 18:34:49 +04:00
github-actions
13cc722110 📝 Update release notes 2023-10-26 14:32:59 +00:00
Sebastián Ramírez
7fdfee10a5 🔧 Adopt Ruff for formatting (#679) 2023-10-26 18:32:26 +04:00
github-actions
8d14232538 📝 Update release notes 2023-10-26 10:18:41 +00:00
Daniil Fajnberg
99f8ce3894 Add support for all Field parameters from Pydantic 1.9.0 and above, make Pydantic 1.9.0 the minimum required version (#440)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-26 14:18:05 +04:00
Sebastián Ramírez
d05c3ee495 🔖 Release version 0.0.9 2023-10-24 01:01:18 +04:00
Sebastián Ramírez
596718d93b 📝 Update release notes 2023-10-24 00:59:49 +04:00
github-actions
a6ce817ca5 📝 Update release notes 2023-10-23 20:46:39 +00:00
github-actions
dcc4e4c36a 📝 Update release notes 2023-10-23 20:46:26 +00:00
Simon Weiß
80fd7e03cf 📝 Clarify description of in-memory SQLite database in docs/tutorial/create-db-and-table.md (#601)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 20:46:05 +00:00
pre-commit-ci[bot]
1acb683f80 ⬆ [pre-commit.ci] pre-commit autoupdate (#672)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)
- [github.com/asottile/pyupgrade: v3.7.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.7.0...v3.15.0)
- https://github.com/charliermarsh/ruff-pre-commithttps://github.com/astral-sh/ruff-pre-commit
- [github.com/astral-sh/ruff-pre-commit: v0.0.275 → v0.1.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.275...v0.1.1)
- [github.com/psf/black: 23.3.0 → 23.10.0](https://github.com/psf/black/compare/23.3.0...23.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-24 00:45:45 +04:00
github-actions
9fd7306648 📝 Update release notes 2023-10-23 20:37:48 +00:00
Sebastián Ramírez
9d3ca01dd0 📝 Tweak wording in docs/tutorial/fastapi/multiple-models.md (#674)
Co-authored-by: Luis Benitez <lbenitez000@gmail.com>
2023-10-23 20:37:07 +00:00
github-actions
6cd086f25f 📝 Update release notes 2023-10-23 19:22:23 +00:00
PookieBuns
376603efb2 ✏️ Fix contributing instructions to run tests, update script name (#634)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 19:21:38 +00:00
github-actions
30d2b30217 📝 Update release notes 2023-10-23 17:22:58 +00:00
Abenezer Belachew
9b186c89a8 📝 Update link to docs for intro to databases (#593)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 17:22:22 +00:00
github-actions
fc3120a877 📝 Update release notes 2023-10-23 17:06:30 +00:00
dependabot[bot]
6d361e3ffb ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.28.0 (#660)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2.24.2 to 2.28.0.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v2.24.2...v2.28.0)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 21:05:55 +04:00
github-actions
03f295b397 📝 Update release notes 2023-10-23 16:56:29 +00:00
Jon Michaelchuck
403d44ea78 📝 Update docs, use offset in example with limit and where (#273)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 20:55:53 +04:00
github-actions
a1caaa08d7 📝 Update release notes 2023-10-23 16:29:36 +00:00
Jerry Wu
d192142eb9 📝 Fix docs for Pydantic's fields using le (lte is invalid, use le ) (#207)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 16:28:51 +00:00
github-actions
beb7a24275 📝 Update release notes 2023-10-23 15:33:44 +00:00
Sebastián Ramírez
dee70033b8 Refactor OpenAPI FastAPI tests to simplify updating them later, this moves things around without changes (#671) 2023-10-23 15:33:08 +00:00
github-actions
189059e07e 📝 Update release notes 2023-10-23 15:32:48 +00:00
dependabot[bot]
475b838c8b ⬆ Bump actions/checkout from 3 to 4 (#670)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 19:32:09 +04:00
github-actions
d6e4f9b9e3 📝 Update release notes 2023-10-23 15:16:53 +00:00
Michael Oliver
1062e1b485 🔧 Update mypy config, use strict = true instead of manual configs (#428)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 19:16:17 +04:00
github-actions
8e55ea5125 📝 Update release notes 2023-10-23 14:58:52 +00:00
Arseny Boykov
9732c5ac60 🐛 Fix AsyncSession type annotations for exec() (#58)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 18:58:16 +04:00
github-actions
b8996f0e62 📝 Update release notes 2023-10-23 13:59:53 +00:00
Daniil Fajnberg
9809b5bc83 🐛 Fix allowing using a ForeignKey directly, remove repeated column construction from SQLModelMetaclass.__init__ and upgrade minimum SQLAlchemy to >=1.4.36 (#443)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 17:59:06 +04:00
github-actions
c213f5daf4 📝 Update release notes 2023-10-23 11:22:53 +00:00
Sandro Tosi
d1cf613461 ⬆️ Upgrade support for SQLAlchemy 1.4.49, update tests (#519)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-23 15:22:10 +04:00
github-actions
d281a0fa9f 📝 Update release notes 2023-10-23 09:40:31 +00:00
Daniil Fajnberg
9511c4677d ⬆ Raise SQLAlchemy version requirement to at least 1.4.29 (related to #434) (#439)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 13:39:55 +04:00
github-actions
c25a8cb89b 📝 Update release notes 2023-10-23 09:23:24 +00:00
Sebastián Ramírez
d3261cab59 🐛 Fix enum type checks ordering in get_sqlalchemy_type (#669)
Co-authored-by: Pierre Cheynier <p.cheynier@criteo.com>
2023-10-23 13:22:44 +04:00
github-actions
40c1af9202 📝 Update release notes 2023-10-23 08:15:40 +00:00
Sebastián Ramírez
b83e848699 ⬆️ Upgrade MkDocs Material (#668) 2023-10-23 08:15:05 +00:00
github-actions
2676cf2b06 📝 Update release notes 2023-10-23 08:12:08 +00:00
Sebastián Ramírez
56f43904c1 🎨 Update docs format and references with pre-commit and Ruff (#667) 2023-10-23 08:11:36 +00:00
github-actions
7c5894ee75 📝 Update release notes 2023-10-23 07:47:04 +00:00
Sebastián Ramírez
27a81b2112 🎨 Run pre-commit on all files and autoformat (#666) 2023-10-23 11:46:31 +04:00
github-actions
7f72c60ae4 📝 Update release notes 2023-10-23 07:35:25 +00:00
Sebastián Ramírez
065fcdc828 👷 Move to Ruff and add pre-commit (#661)
* 👷 Add pre-commit

* 🔧 Add pyproject.toml config for Ruff

*  Replace isort, flake8, autoflake with Ruff

* 🔨 Update lint and format scripts

* 🎨 Format with Ruff

* 🔧 Update Poetry config
2023-10-23 07:34:50 +00:00
github-actions
9ba3039106 📝 Update release notes 2023-10-23 06:43:04 +00:00
David Danier
840fd08ab2 Raise a more clear error when a type is not valid (#425)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-23 10:42:30 +04:00
github-actions
a8a792e3c0 📝 Update release notes 2023-10-22 12:55:08 +00:00
Matvey Fedoseev
8970833b14 📝 Update outdated link in docs/db-to-code.md (#649)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-22 12:54:36 +00:00
github-actions
5231c8b6bb 📝 Update release notes 2023-10-22 12:51:20 +00:00
Kian-Meng Ang
1568bad01e ✏️ Fix typos found with codespell (#520)
Found via `codespell -S *.svg,*.css,*.js,*.drawio -L pullrequest,sesion`
2023-10-22 16:50:44 +04:00
github-actions
357417e6d5 📝 Update release notes 2023-10-22 12:47:27 +00:00
Dipendra Raj Panta
893d64fd31 📝 Fix typos (duplication) in main page (#631)
* Update README.md

* Update index.md
2023-10-22 16:46:52 +04:00
github-actions
2799303f6c 📝 Update release notes 2023-10-22 12:27:13 +00:00
Ben Rapaport
a2e2942aad 📝 Update release notes, add second author to PR (#429)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-22 12:26:37 +00:00
github-actions
b7d6a0a3c3 📝 Update release notes 2023-10-22 12:04:24 +00:00
Jorge Alvarado
bdcf11bca6 📝 Update instructions about how to make a foreign key required in docs/tutorial/relationship-attributes/define-relationships-attributes.md (#474)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-22 16:03:51 +04:00
github-actions
d939b7c45c 📝 Update release notes 2023-10-22 12:02:24 +00:00
byrman
d5219aa3c5 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315) (#461)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-22 16:01:51 +04:00
github-actions
aa7169b93b 📝 Update release notes 2023-10-22 10:03:26 +00:00
Sugato Ray
89c356cb77 🛠️ Add CITATION.cff file for academic citations (#13)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-10-22 14:02:53 +04:00
github-actions
088164ef2a 📝 Update release notes 2023-08-01 09:19:41 +00:00
Sebastián Ramírez
1e2fd10047 👷 Update docs deployments to Cloudflare (#630) 2023-08-01 11:18:53 +02:00
github-actions
30a9c23a34 📝 Update release notes 2023-07-31 19:53:00 +00:00
github-actions
da29f7940d 📝 Update release notes 2023-07-31 19:52:51 +00:00
github-actions
40007c80da 📝 Update release notes 2023-07-31 19:48:54 +00:00
Sebastián Ramírez
e246ae8864 👷 Update CI debug mode with Tmate (#629) 2023-07-31 19:48:21 +00:00
github-actions
aaa54492c1 📝 Update release notes 2023-07-31 19:39:15 +00:00
Sebastián Ramírez
73fa81af74 👷 Update latest changes token (#616) 2023-07-31 19:38:43 +00:00
Sebastián Ramírez
a21d5c85a3 👷‍♂️ Upgrade CI for docs (#628) 2023-07-31 17:15:43 +02:00
Sebastián Ramírez
02bd7ebffd 🗑️ Deprecate Python 3.6 and upgrade Poetry and Poetry Version Plugin (#627) 2023-07-29 12:32:47 +02:00
github-actions
43a689d369 📝 Update release notes 2023-02-21 11:02:54 +00:00
Sebastián Ramírez
810236c26c ⬆️ Upgrade analytics (#558) 2023-02-21 11:02:18 +00:00
github-actions
33e00c3ab3 📝 Update release notes 2023-02-03 17:53:01 +00:00
Sebastián Ramírez
5c9a3b3b21 📝 Update help SQLModel docs (#548) 2023-02-03 18:52:25 +01:00
github-actions
dc44d2e2c4 📝 Update release notes 2023-01-31 14:14:55 +00:00
Sebastián Ramírez
1c294ddeb6 🔧 Update new issue chooser to point to GitHub Discussions (#546) 2023-01-31 15:14:16 +01:00
github-actions
c47a54df91 📝 Update release notes 2023-01-30 10:50:32 +00:00
Sebastián Ramírez
624a2142bf 🔧 Add template for GitHub Discussion questions and update issues template (#544) 2023-01-30 11:49:56 +01:00
github-actions
7b3148c0b4 📝 Update release notes 2022-12-16 18:46:26 +00:00
Sebastián Ramírez
cf36b2d9ba 👷 Refactor CI artifact upload/download for docs previews (#514) 2022-12-16 18:45:51 +00:00
github-actions
5fa9062ed9 📝 Update release notes 2022-11-12 06:44:52 +00:00
Colin Marquardt
267cd42fb6 ✏️ Fix typo in internal function name get_sqlachemy_type() (#496)
Corrected name is get_sqlalchemy_type().
2022-11-12 07:44:19 +01:00
github-actions
497270dc55 📝 Update release notes 2022-11-12 06:38:16 +00:00
dependabot[bot]
aa87ff37ea ⬆ Bump actions/cache from 2 to 3 (#497)
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-12 07:37:34 +01:00
github-actions
0fe1e6d5a3 📝 Update release notes 2022-11-11 17:30:42 +00:00
David Brochart
54b5db9842 ✏️ Fix typo in docs (#446)
* Fix typo

* Again

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-11 17:30:09 +00:00
github-actions
c31bac638c 📝 Update release notes 2022-11-10 06:43:44 +00:00
dependabot[bot]
203db14e9b ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.2 (#493)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2.24.0 to 2.24.2.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v2.24.0...v2.24.2)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-10 07:43:06 +01:00
github-actions
06bb369bcb 📝 Update release notes 2022-11-08 15:04:40 +00:00
Santhosh Solomon
209791734c ✏️ Fix typo in docs/tutorial/create-db-and-table.md (#477)
typo fixed in docs/tutorial/create-db-and-table.md

Co-authored-by: Santhosh Solomon <santhoshsolomon@Santhoshs-MacBook-Air.local>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-08 15:04:06 +00:00
github-actions
fbc6f3b536 📝 Update release notes 2022-11-07 14:21:59 +00:00
Michael Cuffaro
22faa192b9 ✏️ Fix small typos in docs (#481)
Fix small typos

Fixing small typos here and there while reading the document.

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-07 14:21:23 +00:00
github-actions
3a9244c69f 📝 Update release notes 2022-11-05 01:04:44 +00:00
Sebastián Ramírez
8070b142b8 🔧 Update Smokeshow coverage threshold (#487) 2022-11-05 02:04:13 +01:00
github-actions
f0aa199611 📝 Update release notes 2022-11-04 22:02:09 +00:00
Sebastián Ramírez
5956940908 👷 Move from Codecov to Smokeshow (#486) 2022-11-04 22:01:37 +00:00
github-actions
ea79c47c0e 📝 Update release notes 2022-11-04 21:44:25 +00:00
dependabot[bot]
7e26dac198 ⬆ Bump actions/setup-python from 2 to 4 (#411)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-04 22:43:45 +01:00
github-actions
bb32178bda 📝 Update release notes 2022-11-04 21:32:51 +00:00
dependabot[bot]
5ca9375f26 ⬆ Update black requirement from ^21.5-beta.1 to ^22.10.0 (#460)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-04 22:32:13 +01:00
github-actions
e7d8b69b53 📝 Update release notes 2022-11-04 21:18:47 +00:00
Sebastián Ramírez
c6ad5b8109 Add extra dev dependencies for MkDocs Material (#485) 2022-11-04 22:16:59 +01:00
github-actions
8003c73877 📝 Update release notes 2022-11-04 21:14:56 +00:00
github-actions
b532a4c304 📝 Update release notes 2022-11-04 21:14:34 +00:00
dependabot[bot]
29d9721d1a ⬆ Update mypy requirement from 0.930 to 0.971 (#380)
Update mypy requirement from 0.930 to 0.971

Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.930...v0.971)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-04 22:14:11 +01:00
dependabot[bot]
f0f6f93e28 ⬆ Update coverage requirement from ^5.5 to ^6.2 (#171)
Update coverage requirement from ^5.5 to ^6.2

Updates the requirements on [coverage](https://github.com/nedbat/coveragepy) to permit the latest version.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.5...6.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-04 22:13:58 +01:00
github-actions
91d8e38208 📝 Update release notes 2022-11-04 21:03:39 +00:00
github-actions
ccdab8fb24 📝 Update release notes 2022-11-04 21:02:59 +00:00
dependabot[bot]
592c877cfc ⬆ Bump codecov/codecov-action from 2 to 3 (#415)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-04 22:00:58 +01:00
dependabot[bot]
fee7ecf619 ⬆ Bump actions/upload-artifact from 2 to 3 (#412)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-04 22:00:21 +01:00
github-actions
e8f61fb9d0 📝 Update release notes 2022-11-04 21:00:05 +00:00
dependabot[bot]
781174e6e9 ⬆ Update flake8 requirement from ^3.9.2 to ^5.0.4 (#396)
Updates the requirements on [flake8](https://github.com/pycqa/flake8) to permit the latest version.
- [Release notes](https://github.com/pycqa/flake8/releases)
- [Commits](https://github.com/pycqa/flake8/compare/3.9.2...5.0.4)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-04 21:59:26 +01:00
github-actions
ba76745f43 📝 Update release notes 2022-11-04 20:45:07 +00:00
github-actions
5615a80fc5 📝 Update release notes 2022-11-04 20:44:06 +00:00
dependabot[bot]
375e83d068 ⬆ Update pytest requirement from ^6.2.4 to ^7.0.1 (#242)
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.4...7.0.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-04 21:44:02 +01:00
github-actions
666a9a3557 📝 Update release notes 2022-11-04 20:43:32 +00:00
dependabot[bot]
1dde3e0044 ⬆ Bump actions/checkout from 2 to 3.1.0 (#458)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.1.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3.1.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-04 21:43:25 +01:00
dependabot[bot]
8b87bf8b40 ⬆ Bump dawidd6/action-download-artifact from 2.9.0 to 2.24.0 (#470)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2.9.0 to 2.24.0.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v2.9.0...v2.24.0)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-04 21:43:01 +01:00
github-actions
94715b6fa8 📝 Update release notes 2022-11-04 20:05:07 +00:00
Sebastián Ramírez
27a16744f2 👷 Update Dependabot config (#484) 2022-11-04 21:04:31 +01:00
Sebastián Ramírez
75ce45588b 🔖 Release version 0.0.8 2022-08-30 19:52:36 +02:00
Sebastián Ramírez
b3e1a66a21 🔖 Release version 0.0.8 2022-08-30 19:47:41 +02:00
Sebastián Ramírez
c94db7b8a0 📝 Update release notes 2022-08-30 19:46:58 +02:00
github-actions
a67326d358 📝 Update release notes 2022-08-30 16:36:07 +00:00
Sebastián Ramírez
e88b5d3691 📝 Adjust and clarify docs for docs/tutorial/create-db-and-table.md (#426) 2022-08-30 16:35:29 +00:00
github-actions
fdb049bee3 📝 Update release notes 2022-08-30 16:19:19 +00:00
Jonas Krüger Svensson
ae144e0a39 🐛 Fix auto detecting and setting nullable, allowing overrides in field (#423)
Co-authored-by: Benjamin Rapaport <br@getallstreet.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-30 18:18:32 +02:00
github-actions
85f5e7fc45 📝 Update release notes 2022-08-29 09:44:50 +00:00
Sebastián Ramírez
b51ebaf658 ♻️ Update expresion.py, sync from Jinja2 template, implement inherit_cache to solve errors like: SAWarning: Class SelectOfScalar will not make use of SQL compilation caching (#422) 2022-08-29 11:44:08 +02:00
github-actions
f232166db5 📝 Update release notes 2022-08-29 08:34:17 +00:00
Theodore Williams
4143edd251 ✏ Fix typo in docs/tutorial/connect/remove-data-connections.md (#421) 2022-08-29 10:33:41 +02:00
Sebastián Ramírez
f9522b3913 🔖 Release version 0.0.7 2022-08-28 01:59:44 +02:00
Sebastián Ramírez
e7848923ec 📝 Update release notes 2022-08-28 01:59:04 +02:00
github-actions
eb12bbc640 📝 Update release notes 2022-08-27 23:53:33 +00:00
Yasser Tahiri
6216409f96 ♻ Refactor internal statements to simplify code (#53) 2022-08-28 01:53:02 +02:00
github-actions
6fe256ec2c 📝 Update release notes 2022-08-27 23:50:48 +00:00
Amin Alaee
92f52a3fc5 ♻ Refactor internal imports to reduce redundancy (#272)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:50:12 +00:00
github-actions
2bc915ed04 📝 Update release notes 2022-08-27 23:50:09 +00:00
Raphael Gibson
42b0e6eace Allow setting unique in Field() for a column (#83)
Co-authored-by: Raphael Gibson <raphael.araujo@estantemagica.com.br>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:49:29 +02:00
github-actions
1ca288028c 📝 Update release notes 2022-08-27 23:44:16 +00:00
kurtportelli
a2cda8377f 📝 Update docs for models for updating, id should not be updatable (#335)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:43:42 +02:00
github-actions
8ac82e7101 📝 Update release notes 2022-08-27 23:22:53 +00:00
phi-friday
5429e9b6aa 🐛 Fix type annotations for Model.parse_obj(), and Model.validate() (#321)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:22:09 +02:00
github-actions
c743647a52 📝 Update release notes 2022-08-27 23:18:19 +00:00
Rabin Adhikari
475578757f 🐛 Fix Select and SelectOfScalar to inherit cache to avoid warning: SAWarning: Class SelectOfScalar will not make use of SQL compilation caching (#234)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:17:37 +02:00
github-actions
5e0ac5b56c 📝 Update release notes 2022-08-27 23:11:00 +00:00
byrman
d380736043 🐛 Fix handling validators for non-default values (#253)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:10:23 +00:00
github-actions
71d6fcc31b 📝 Update release notes 2022-08-27 22:59:54 +00:00
statt8900
680602b7eb 🐛 Fix fields marked as "set" in models (#117)
Co-authored-by: Michael Statt <michael.statt@modelyst.io>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 22:59:09 +00:00
github-actions
9c68ce12ec 📝 Update release notes 2022-08-27 22:49:17 +00:00
Chris White
eef0b7770b 🐛 Fix Enum handling in SQLAlchemy (#165)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 00:48:44 +02:00
github-actions
2fab4817fe 📝 Update release notes 2022-08-27 22:28:46 +00:00
Andrew Bolster
5ea9340def Update GUID handling to use stdlib UUID.hex instead of an int (#26)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 00:28:09 +02:00
github-actions
db3ad598c5 📝 Update release notes 2022-08-27 22:19:35 +00:00
Evangelos Anagnostopoulos
9830ee0d89 🐛 Fix setting nullable property of Fields that don't accept None (#79)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 00:18:57 +02:00
github-actions
2407ecd2bf 📝 Update release notes 2022-08-27 22:07:38 +00:00
Yoann Mosteiro
ee576ab279 ✏ Fix broken variable/typo in docs for Read Relationships, hero_spider_boy.id => hero_spider_boy.team_id (#106)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 22:06:56 +00:00
github-actions
ae1b8b5585 📝 Update release notes 2022-08-27 21:55:59 +00:00
Jorge Alvarado
e48fb2874b 🎨 Remove unwanted highlight in the docs (#233)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:55:15 +02:00
github-actions
7d3bf70a76 📝 Update release notes 2022-08-27 21:37:31 +00:00
Prashanth Rao
91d0785b1c ✏ Fix typos in docs/databases.md and docs/tutorial/index.md (#35)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:36:58 +02:00
github-actions
5dfef7ede7 📝 Update release notes 2022-08-27 21:32:10 +00:00
Jorge Alvarado
0aaf39d539 ✏ Fix typo in docs/tutorial/relationship-attributes/define-relationships-attributes.md (#239)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:31:38 +02:00
github-actions
c0a6b2dd8b 📝 Update release notes 2022-08-27 21:26:06 +00:00
Joe Mudryk
87a02b4c46 ✏ Fix typo in docs/tutorial/fastapi/simple-hero-api.md (#80)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 21:25:29 +00:00
github-actions
14fc1f510e 📝 Update release notes 2022-08-27 21:23:41 +00:00
VictorGambarini
ad0766fe3e ✏ Fix typos in multiple files in the docs (#400)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:22:59 +02:00
github-actions
1e69c00538 📝 Update release notes 2022-08-27 21:09:00 +00:00
github-actions
bf15380733 📝 Update release notes 2022-08-27 21:08:31 +00:00
Marcio Mazza
a993c2141d ✏ Fix typo in docs/tutorial/code-structure.md (#344)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:08:20 +02:00
gr8jam
deed65095f ✏ Fix typo in docs/db-to-code.md (#155)
Co-authored-by: gr8jam <matej.jeglic@gmail.si>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:07:48 +02:00
github-actions
61294af824 📝 Update release notes 2022-08-27 21:06:58 +00:00
Fardad13
106fb1fe9b ✏ Fix typo in docs/contributing.md (#323)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:06:15 +02:00
github-actions
6b433a0de4 📝 Update release notes 2022-08-27 21:05:21 +00:00
Jack Homan
4de5a41720 ✏ Fix typo in docs/tutorial/fastapi/tests.md (#265)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:04:38 +02:00
github-actions
dc5876c727 📝 Update release notes 2022-08-27 21:02:59 +00:00
github-actions
5f6b5bfd7f 📝 Update release notes 2022-08-27 21:02:37 +00:00
Jorge Alvarado
e5fdc371f6 ✏ Fix typo in docs/tutorial/where.md (#286)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:00:53 +02:00
cirrusj
452f18d8bc ✏ Fix typos in docs/tutorial/fastapi/update.md (#268)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:00:09 +02:00
github-actions
04b8b3eedf 📝 Update release notes 2022-08-27 20:56:00 +00:00
Hao Wang
426da7c443 ✏ Fix typo in docs/tutorial/fastapi/simple-hero-api.md (#247)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:55:27 +00:00
github-actions
a5116a372c 📝 Update release notes 2022-08-27 20:54:16 +00:00
Gal Bracha
015f7acbc5 ✏ Fix typos in docs/tutorial/automatic-id-none-refresh.md, docs/tutorial/fastapi/update.md, docs/tutorial/select.md (#185)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:53:34 +00:00
github-actions
34e125357f 📝 Update release notes 2022-08-27 20:53:09 +00:00
github-actions
c0efc7b370 📝 Update release notes 2022-08-27 20:52:33 +00:00
Sean Eulenberg
48ada0cd5d ✏ Fix typo in docs/databases.md (#177)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:52:24 +00:00
wmcgee3
aa5803fbbb ✏ Fix typos in docs/tutorial/fastapi/update.md (#162)
Co-authored-by: pwildenhain <35195136+pwildenhain@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:51:46 +00:00
github-actions
8bee55e23b 📝 Update release notes 2022-08-27 20:51:10 +00:00
Måns Magnusson
6f1ffccd4f ✏ Fix typos in docs/tutorial/code-structure.md, docs/tutorial/fastapi/multiple-models.md, docs/tutorial/fastapi/simple-hero-api.md, docs/tutorial/many-to-many/index.md (#116)
Co-authored-by: moonso <mans.magnusson@scilifelab.se>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:50:33 +00:00
github-actions
13544c0f44 📝 Update release notes 2022-08-27 20:48:52 +00:00
Chris Goddard
184c8eb5a9 ✏ Fix typo in docs/tutorial/fastapi/teams.md (#154)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:48:09 +00:00
github-actions
acc27dabc9 📝 Update release notes 2022-08-27 20:41:29 +00:00
Saman Nezafat
d032c3cfea ✏ Fix typo variable in example about relationships and back_populates, always use hero instead of owner (#120)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:40:57 +00:00
github-actions
006cf488e8 📝 Update release notes 2022-08-27 20:34:14 +00:00
Feanil Patel
63dd44dc86 ✏ Fix typo in docs/tutorial/fastapi/tests.md (#113)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:33:41 +00:00
github-actions
f602794f07 📝 Update release notes 2022-08-27 20:33:32 +00:00
Fedor Kuznetsov
f3063a8e16 ✏ Fix typo in docs/tutorial/where.md (#72)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:32:58 +00:00
github-actions
f67a13a5fb 📝 Update release notes 2022-08-27 20:32:46 +00:00
Dhiraj Gupta
5dff4d15e8 ✏ Fix typo in docs/tutorial/code-structure.md (#91)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:32:02 +00:00
github-actions
943892ddb2 📝 Update release notes 2022-08-27 20:31:46 +00:00
mborus
31beaf1017 ✏ Fix broken link to newsletter sign-up in docs/help.md (#84)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:30:59 +00:00
github-actions
9664c8814c 📝 Update release notes 2022-08-27 20:20:44 +00:00
Brent
7bb99f2bd5 ⬆ Update development requirement for FastAPI from ^0.68.0 to ^0.68.1 (#48)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:20:05 +00:00
github-actions
4a08ee89ee 📝 Update release notes 2022-08-27 20:15:17 +00:00
xginn8
0197c6e211 ✏ Fix typos in docs/tutorial/many-to-many/create-models-with-link.md (#45)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:14:23 +00:00
github-actions
6da8dcfc8e 📝 Update release notes 2022-08-27 20:14:11 +00:00
Jakob Jul Elben
dc4dc42ec5 Raise an exception when using a Pydantic field type with no matching SQLAlchemy type (#18)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:13:32 +00:00
github-actions
db29f53295 📝 Update release notes 2022-08-27 20:09:33 +00:00
Sebastián Ramírez
bc6dc0bafc Revert upgrade Poetry, to make a release that supports Python 3.6 first (#417) 2022-08-27 22:08:25 +02:00
github-actions
36b0c1ba08 📝 Update release notes 2022-08-27 18:40:28 +00:00
github-actions
dc0ecbb2c2 📝 Update release notes 2022-08-27 18:40:20 +00:00
Sebastián Ramírez
aca18da21e 👷 Add dependabot for GitHub Actions (#410) 2022-08-27 20:39:53 +02:00
Sebastián Ramírez
f7d1bbe5b6 ⬆️ Upgrade Poetry to version ==1.2.0b1 (#303) 2022-08-27 20:39:37 +02:00
github-actions
296a0935d1 📝 Update release notes 2022-08-27 18:36:49 +00:00
Ryan Grose
0049436cd4 ✏ Fix typo in docs/tutorial/index.md (#398)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 18:36:08 +00:00
github-actions
f4500c6ba4 📝 Update release notes 2022-08-27 18:22:18 +00:00
Robert Rosca
c830c71e28 ⬆ Upgrade constrain for SQLAlchemy = ">=1.4.17,<=1.4.41" (#371)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:21:38 +02:00
github-actions
ea18162391 📝 Update release notes 2022-08-27 18:11:12 +00:00
byrman
4dd7b890d4 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315) (#322)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:10:38 +02:00
github-actions
4d20051793 📝 Update release notes 2022-04-16 09:30:54 +00:00
Sebastián Ramírez
88683f6e2c 👷 Add CI for Python 3.10 (#305) 2022-04-16 09:30:19 +00:00
github-actions
d6229b3937 📝 Update release notes 2022-04-16 09:25:22 +00:00
Sebastián Ramírez
e523e1e4c3 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions (#263)
Co-authored-by: yanlong.wang <yanlong.wang@naiver.org>
Co-authored-by: Han Xiao <han.xiao@jina.ai>
2022-04-16 11:24:53 +02:00
github-actions
6d969c5845 📝 Update release notes 2022-04-16 09:20:01 +00:00
Sebastián Ramírez
b94d393924 👷 Upgrade Codecov GitHub Action (#304) 2022-04-16 11:19:30 +02:00
github-actions
e009ecb704 📝 Update release notes 2022-04-16 09:13:48 +00:00
Sebastián Ramírez
03e861d048 Add new Session.get() parameter execution_options (#302) 2022-04-16 11:13:19 +02:00
github-actions
8e97c93de0 📝 Update release notes 2022-02-13 17:14:34 +00:00
Sebastián Ramírez
7176d89e48 💚 Only run CI on push when on master, to avoid duplicate runs on PRs (#244) 2022-02-13 17:14:02 +00:00
github-actions
e6f8c00bbe 📝 Update release notes 2022-01-08 16:54:48 +00:00
Sebastián Ramírez
c873aa3930 🔧 Upgrade MkDocs Material and update configs (#217) 2022-01-08 17:49:07 +01:00
github-actions
800a5f232f 📝 Update release notes 2022-01-08 16:37:03 +00:00
Sebastián Ramírez
8d1b6f079a ⬆ Upgrade mypy, fix type annotations (#218) 2022-01-08 16:36:19 +00:00
Sebastián Ramírez
7fcd4fd7c5 🔖 Release version 0.0.6 2021-12-28 12:27:33 +01:00
Sebastián Ramírez
9203df6af1 📝 Update release notes 2021-12-28 12:26:52 +01:00
github-actions
d6d77a9ee4 📝 Update release notes 2021-12-28 10:48:37 +00:00
Sebastián Ramírez
155c6178cd Document indexes and make them opt-in (#205) 2021-12-28 11:48:03 +01:00
github-actions
3d7b74746c 📝 Update release notes 2021-12-14 18:25:46 +00:00
Yaqueline Hoyos
410d7af6b6 ✏ Fix typo in FastAPI tutorial (#192) 2021-12-14 19:25:06 +01:00
github-actions
1b99c3148f 📝 Update release notes 2021-12-14 17:30:07 +00:00
Nikita Sobolev
6cf94a9797 📝 Add links to the license file (#29)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-12-14 18:29:28 +01:00
github-actions
a159f31945 📝 Update release notes 2021-12-14 17:21:35 +00:00
Alexandre Batisse
dc3acda4ed ✏ Fix typos in docs titles (#28) 2021-12-14 18:20:54 +01:00
github-actions
ead1bdc532 📝 Update release notes 2021-12-14 17:17:55 +00:00
Evan Grim
2013c69c4d ✏ Fix multiple typos and some rewording (#22)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-12-14 18:17:10 +01:00
github-actions
6615b111d9 📝 Update release notes 2021-12-14 17:12:17 +00:00
Leynier Gutiérrez González
50e62cdcd9 ✏ Fix typo in docs/tutorial/automatic-id-none-refresh.md (#14) 2021-12-14 18:11:39 +01:00
github-actions
64d7f53357 📝 Update release notes 2021-12-14 16:59:16 +00:00
Sebastian Marines
32b5b39f2d ✏ Fix typos in docs/tutorial/index.md and docs/databases.md (#5)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-12-14 17:58:40 +01:00
233 changed files with 7140 additions and 3917 deletions

View File

@@ -1,5 +1,3 @@
name: Question or Problem
description: Ask a question or ask about a problem
labels: [question]
body:
- type: markdown
@@ -8,29 +6,29 @@ body:
Thanks for your interest in SQLModel! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title to this issue.
- label: I added a very descriptive title here.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
- label: I used the GitHub search to find a similar question and didn't find it.
required: true
- label: I searched the SQLModel documentation, with the integrated search.
required: true
@@ -48,10 +46,10 @@ body:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
* Review one Pull Request by downloading the code and following all the review process](https://sqlmodel.tiangolo.com/help/#review-pull-requests).
options:
- label: I commit to help with one of those options 👆

View File

@@ -2,3 +2,12 @@ blank_issues_enabled: false
contact_links:
- name: Security Contact
about: Please report security vulnerabilities to security@tiangolo.com
- name: Question or Problem
about: Ask a question or ask about a problem in GitHub Discussions.
url: https://github.com/tiangolo/sqlmodel/discussions/categories/questions
- name: Feature Request
about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already.
url: https://github.com/tiangolo/sqlmodel/discussions/categories/questions
- name: Show and tell
about: Show what you built with SQLModel or to be used with SQLModel.
url: https://github.com/tiangolo/sqlmodel/discussions/categories/show-and-tell

View File

@@ -1,214 +0,0 @@
name: Feature Request
description: Suggest an idea or ask for a feature that you would like to have in SQLModel
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in SQLModel! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title to this issue.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
required: true
- label: I searched the SQLModel documentation, with the integrated search.
required: true
- label: I already searched in Google "How to X in SQLModel" and didn't find any information.
required: true
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
required: true
- label: I already checked if it is not related to SQLModel but to [Pydantic](https://github.com/samuelcolvin/pydantic).
required: true
- label: I already checked if it is not related to SQLModel but to [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy).
required: true
- type: checkboxes
id: help
attributes:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
options:
- label: I commit to help with one of those options 👆
required: true
- type: textarea
id: example
attributes:
label: Example Code
description: |
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
placeholder: |
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
print(hero_1)
render: python
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
What is your feature request?
Write a short description telling me what you are trying to solve and what you are currently doing.
placeholder: |
* Create a Hero model.
* Create a Hero instance.
* Save it to a SQLite database.
* I would like it to also automatically send me an email with all the SQL code executed.
validations:
required: true
- type: textarea
id: wanted-solution
attributes:
label: Wanted Solution
description: |
Tell me what's the solution you would like.
placeholder: |
I would like it to have a `send_email` configuration that defaults to `False`, and can be set to `True` to send me an email.
validations:
required: true
- type: textarea
id: wanted-code
attributes:
label: Wanted Code
description: Show me an example of how you would want the code to look like.
placeholder: |
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine
class Hero(SQLModel, table=True, send_email=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
print(hero_1)
render: python
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: |
Tell me about alternatives you've considered.
placeholder: |
To hire someone to look at the logs, write the SQL in paper, and then send me a letter by post with it.
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you on?
multiple: true
options:
- Linux
- Windows
- macOS
- Other
validations:
required: true
- type: textarea
id: os-details
attributes:
label: Operating System Details
description: You can add more details about your operating system here, in particular if you chose "Other".
- type: input
id: sqlmodel-version
attributes:
label: SQLModel Version
description: |
What SQLModel version are you using?
You can find the SQLModel version with:
```bash
python -c "import sqlmodel; print(sqlmodel.__version__)"
```
validations:
required: true
- type: input
id: python-version
attributes:
label: Python Version
description: |
What Python version are you using?
You can find the Python version with:
```bash
python --version
```
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any additional context information or screenshots you think are useful.

22
.github/ISSUE_TEMPLATE/privileged.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Privileged
description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇
body:
- type: markdown
attributes:
value: |
Thanks for your interest in SQLModel! 🚀
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/sqlmodel/discussions/categories/questions) instead.
- type: checkboxes
id: privileged
attributes:
label: Privileged issue
description: Confirm that you are allowed to create an issue here.
options:
- label: I'm @tiangolo or he asked me directly to create an issue here.
required: true
- type: textarea
id: content
attributes:
label: Issue Content
description: Add the content of the issue here.

View File

@@ -48,9 +48,7 @@ if __name__ == "__main__":
use_pr = pr
break
if not use_pr:
logging.error(
f"No PR found for hash: {event.workflow_run.head_commit.id}"
)
logging.error(f"No PR found for hash: {event.workflow_run.head_commit.id}")
sys.exit(0)
github_headers = {
"Authorization": f"token {settings.input_token.get_secret_value()}"

View File

@@ -1,7 +0,0 @@
FROM python:3.7
RUN pip install httpx PyGithub "pydantic==1.5.1"
COPY ./app /app
CMD ["python", "/app/main.py"]

View File

@@ -1,10 +0,0 @@
name: Watch docs previews in PRs
description: Check PRs and trigger new docs deploys
author: "Sebastián Ramírez <tiangolo@gmail.com>"
inputs:
token:
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
runs:
using: docker
image: Dockerfile

View File

@@ -1,102 +0,0 @@
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Optional
import httpx
from github import Github
from github.NamedUser import NamedUser
from pydantic import BaseModel, BaseSettings, SecretStr
github_api = "https://api.github.com"
netlify_api = "https://api.netlify.com"
main_branch = "main"
class Settings(BaseSettings):
input_token: SecretStr
github_repository: str
github_event_path: Path
github_event_name: Optional[str] = None
class Artifact(BaseModel):
id: int
node_id: str
name: str
size_in_bytes: int
url: str
archive_download_url: str
expired: bool
created_at: datetime
updated_at: datetime
class ArtifactResponse(BaseModel):
total_count: int
artifacts: List[Artifact]
def get_message(commit: str) -> str:
return f"Docs preview for commit {commit} at"
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.json()}")
g = Github(settings.input_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
owner: NamedUser = repo.owner
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
prs = list(repo.get_pulls(state="open"))
response = httpx.get(
f"{github_api}/repos/{settings.github_repository}/actions/artifacts",
headers=headers,
)
data = response.json()
artifacts_response = ArtifactResponse.parse_obj(data)
for pr in prs:
logging.info("-----")
logging.info(f"Processing PR #{pr.number}: {pr.title}")
pr_comments = list(pr.get_issue_comments())
pr_commits = list(pr.get_commits())
last_commit = pr_commits[0]
for pr_commit in pr_commits:
if pr_commit.commit.author.date > last_commit.commit.author.date:
last_commit = pr_commit
commit = last_commit.commit.sha
logging.info(f"Last commit: {commit}")
message = get_message(commit)
notified = False
for pr_comment in pr_comments:
if message in pr_comment.body:
notified = True
logging.info(f"Docs preview was notified: {notified}")
if not notified:
artifact_name = f"docs-zip-{commit}"
use_artifact: Optional[Artifact] = None
for artifact in artifacts_response.artifacts:
if artifact.name == artifact_name:
use_artifact = artifact
break
if not use_artifact:
logging.info("Artifact not available")
else:
logging.info(f"Existing artifact: {use_artifact.name}")
response = httpx.post(
f"{github_api}/repos/{settings.github_repository}/actions/workflows/preview-docs.yml/dispatches",
headers=headers,
json={
"ref": main_branch,
"inputs": {
"pr": f"{pr.number}",
"name": artifact_name,
"commit": commit,
},
},
)
logging.info(
f"Trigger sent, response status: {response.status_code} - content: {response.content}"
)
logging.info("Finished")

View File

@@ -1,6 +1,16 @@
version: 2
updates:
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
commit-message:
prefix:
# Python
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
commit-message:
prefix:

View File

@@ -1,79 +1,94 @@
name: Build Docs
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize]
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
types:
- opened
- synchronize
jobs:
changes:
runs-on: ubuntu-latest
# Required permissions
permissions:
pull-requests: read
# Set job outputs to values from filter step
outputs:
docs: ${{ steps.filter.outputs.docs }}
steps:
- uses: actions/checkout@v4
# For pull requests it's not necessary to checkout the code but for the main branch it is
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
docs:
- README.md
- docs/**
- docs_src/**
- pyproject.toml
- mkdocs.yml
- mkdocs.insiders.yml
build-docs:
runs-on: ubuntu-20.04
needs:
- changes
if: ${{ needs.changes.outputs.docs == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.7"
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
with:
limit-access-to-actor: true
- uses: actions/cache@v2
python-version: "3.11"
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-docs
- name: Install poetry
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v01
- name: Install Poetry
if: steps.cache.outputs.cache-hit != 'true'
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
# Ref: https://github.com/python-poetry/poetry-core/pull/188
run: |
python -m pip install --upgrade pip
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
python -m pip install "poetry==1.2.0a2"
python -m poetry plugin add poetry-version-plugin
python -m pip install "poetry"
python -m poetry self add poetry-version-plugin
- name: Configure poetry
run: python -m poetry config virtualenvs.create false
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: python -m poetry install
- name: Install Material for MkDocs Insiders
if: github.event.pull_request.head.repo.fork == false && steps.cache.outputs.cache-hit != 'true'
run: python -m poetry run pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git
- uses: actions/cache@v2
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true'
run: python -m poetry run pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git
- uses: actions/cache@v3
with:
key: mkdocs-cards-${{ github.ref }}
path: .cache
- name: Build Docs
if: github.event.pull_request.head.repo.fork == true
if: github.event_name == 'pull_request' && github.secret_source != 'Actions'
run: python -m poetry run mkdocs build
- name: Build Docs with Insiders
if: github.event.pull_request.head.repo.fork == false
if: github.event_name != 'pull_request' || github.secret_source == 'Actions'
run: python -m poetry run mkdocs build --config-file mkdocs.insiders.yml
- name: Zip docs
run: python -m poetry run bash ./scripts/zip-docs.sh
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: docs-zip
path: ./docs.zip
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1.1.5
name: docs-site
path: ./site/**
# https://github.com/marketplace/actions/alls-green#why
docs-all-green: # This job does nothing and is only used for the branch protection
if: always()
needs:
- build-docs
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
publish-dir: './site'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
jobs: ${{ toJSON(needs) }}
allowed-skips: build-docs

48
.github/workflows/deploy-docs.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Deploy Docs
on:
workflow_run:
workflows:
- Build Docs
types:
- completed
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- name: Clean site
run: |
rm -rf ./site
mkdir ./site
- name: Download Artifact Docs
id: download
uses: dawidd6/action-download-artifact@v2.28.0
with:
if_no_artifact_found: ignore
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
run_id: ${{ github.event.workflow_run.id }}
name: docs-site
path: ./site/
- name: Deploy to Cloudflare Pages
if: steps.download.outputs.found_artifact == 'true'
id: deploy
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: sqlmodel
directory: './site'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
- name: Comment Deploy
if: steps.deploy.outputs.url != ''
uses: ./.github/actions/comment-docs-preview-in-pr
with:
token: ${{ secrets.GITHUB_TOKEN }}
deploy_url: "${{ steps.deploy.outputs.url }}"

View File

@@ -12,27 +12,30 @@ on:
description: PR number
required: true
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
latest-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
# To allow latest-changes to commit to the main branch
token: ${{ secrets.ACTIONS_TOKEN }}
token: ${{ secrets.SQLMODEL_LATEST_CHANGES }}
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: docker://tiangolo/latest-changes:0.0.3
- uses: docker://tiangolo/latest-changes:0.2.0
# - uses: tiangolo/latest-changes@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
latest_changes_file: docs/release-notes.md
latest_changes_header: '## Latest Changes\n\n'
latest_changes_header: '## Latest Changes'
end_regex: '^## '
debug_logs: true
label_header_prefix: '### '

View File

@@ -1,41 +0,0 @@
name: Preview Docs
on:
workflow_run:
workflows:
- Build Docs
types:
- completed
jobs:
preview-docs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Download Artifact Docs
uses: dawidd6/action-download-artifact@v2.9.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
run_id: ${{ github.event.workflow_run.id }}
name: docs-zip
- name: Unzip docs
run: |
rm -rf ./site
unzip docs.zip
rm -f docs.zip
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.1.5
with:
publish-dir: './site'
production-deploy: false
github-token: ${{ secrets.GITHUB_TOKEN }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
- name: Comment Deploy
uses: ./.github/actions/comment-docs-preview-in-pr
with:
token: ${{ secrets.GITHUB_TOKEN }}
deploy_url: "${{ steps.netlify.outputs.deploy-url }}"

View File

@@ -7,40 +7,36 @@ on:
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
publish:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.7"
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: actions/cache@v2
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2
- name: Install poetry
if: steps.cache.outputs.cache-hit != 'true'
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
# Ref: https://github.com/python-poetry/poetry-core/pull/188
run: |
python -m pip install --upgrade pip
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
python -m pip install "poetry==1.2.0a2"
python -m poetry plugin add poetry-version-plugin
python -m pip install "poetry"
python -m poetry self add poetry-version-plugin
- name: Configure poetry
run: python -m poetry config virtualenvs.create false
- name: Install Dependencies

35
.github/workflows/smokeshow.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Smokeshow
on:
workflow_run:
workflows: [Test]
types: [completed]
permissions:
statuses: write
jobs:
smokeshow:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- run: pip install smokeshow
- uses: dawidd6/action-download-artifact@v2.28.0
with:
workflow: test.yml
commit: ${{ github.event.workflow_run.head_sha }}
- run: smokeshow upload coverage-html
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 95
SMOKESHOW_GITHUB_CONTEXT: coverage
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}

View File

@@ -2,59 +2,113 @@ name: Test
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize]
types:
- opened
- synchronize
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
test:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: actions/cache@v2
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2
- name: Install poetry
if: steps.cache.outputs.cache-hit != 'true'
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
# Ref: https://github.com/python-poetry/poetry-core/pull/188
run: |
python -m pip install --upgrade pip
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
python -m pip install "poetry==1.2.0a2"
python -m poetry plugin add poetry-version-plugin
python -m pip install "poetry"
python -m poetry self add poetry-version-plugin
- name: Configure poetry
run: python -m poetry config virtualenvs.create false
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: python -m poetry install
- name: Lint
if: ${{ matrix.python-version != '3.6' }}
# Do not run on Python 3.7 as mypy behaves differently
if: matrix.python-version != '3.7'
run: python -m poetry run bash scripts/lint.sh
- run: mkdir coverage
- name: Test
run: python -m poetry run bash scripts/test.sh
- name: Upload coverage
uses: codecov/codecov-action@v1
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
- name: Store coverage files
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage
coverage-combine:
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Get coverage files
uses: actions/download-artifact@v3
with:
name: coverage
path: coverage
- run: pip install coverage[toml]
- run: ls -la coverage
- run: coverage combine coverage
- run: coverage report
- run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML
uses: actions/upload-artifact@v3
with:
name: coverage-html
path: htmlcov
# https://github.com/marketplace/actions/alls-green#why
alls-green: # This job does nothing and is only used for the branch protection
if: always()
needs:
- coverage-combine
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

25
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,25 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_language_version:
python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-toml
- id: check-yaml
args:
- --unsafe
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
hooks:
- id: ruff
args:
- --fix
- id: ruff-format
ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate

24
CITATION.cff Normal file
View File

@@ -0,0 +1,24 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: SQLModel
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Sebastián
family-names: Ramírez
email: tiangolo@gmail.com
identifiers:
repository-code: 'https://github.com/tiangolo/sqlmodel'
url: 'https://sqlmodel.tiangolo.com'
abstract: >-
SQLModel, SQL databases in Python, designed for
simplicity, compatibility, and robustness.
keywords:
- fastapi
- pydantic
- sqlalchemy
license: MIT

View File

@@ -11,9 +11,8 @@
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
</a>
<a href="https://codecov.io/gh/tiangolo/sqlmodel" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/sqlmodel?color=%2334D058" alt="Coverage">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/sqlmodel" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/sqlmodel.svg" alt="Coverage">
<a href="https://pypi.org/project/sqlmodel" target="_blank">
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
@@ -51,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
## Requirements
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
@@ -212,4 +211,4 @@ And at the same time, ✨ it is also a **Pydantic** model ✨. You can use inher
## License
This project is licensed under the terms of the MIT license.
This project is licensed under the terms of the [MIT license](https://github.com/tiangolo/sqlmodel/blob/main/LICENSE).

View File

@@ -6,10 +6,6 @@ First, you might want to see the basic ways to [help SQLModel and get help](help
If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.
### Python
SQLModel supports Python 3.6 and above, but for development you should have at least **Python 3.7**.
### Poetry
**SQLModel** uses <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> to build, package, and publish the project.
@@ -42,7 +38,7 @@ $ poetry shell
</div>
That will set up the environment variables needed dand will start a new shell with them.
That will set up the environment variables needed and start a new shell with them.
#### Using your local SQLModel
@@ -116,7 +112,7 @@ There is a script that you can run locally to test all the code and generate cov
<div class="termy">
```console
$ bash scripts/test-cov-html.sh
$ bash scripts/test.sh
```
</div>

View File

@@ -85,7 +85,7 @@ Some examples of databases that work like this could be **PostgreSQL**, **MySQL*
### Distributed servers
In some cases, the database could even be a group server applications running on different machines, working together and communicating between them to be more efficient and handle more data.
In some cases, the database could even be a group of server applications running on different machines, working together and communicating between them to be more efficient and handle more data.
In this case, your code would talk to one or more of these server applications running on different machines.
@@ -250,7 +250,7 @@ As these **primary key** IDs can uniquely identify each row on the table for tea
<img alt="table relationships" src="/img/databases/relationships.svg">
So, in the table for heroes, we use the `team_id` column to define a relationship to the *foreign* table for teams. Each value in the `team_id` column on the table with heroes will be the same value as the `id` column of one row in the table wiwth teams.
So, in the table for heroes, we use the `team_id` column to define a relationship to the *foreign* table for teams. Each value in the `team_id` column on the table with heroes will be the same value as the `id` column of one row in the table with teams.
In the table for heroes we have a **primary key** that is the `id`. But we also have another column `team_id` that refers to a **key** in a **foreign** table. There's a technical term for that too, the `team_id` is a "**foreign key**".
@@ -274,7 +274,7 @@ The language is called **SQL**, the name comes from for **Structured Query Langu
Nevertheless, the language is not only used to *query* for data. It is also used to create records/rows, to update them, to delete them. And to manipulate the database, create tables, etc.
This language is supported by all these databases that handle multiple tables, that's why they are called **SQL Databases**. Although, each database has small variations in the SQL language they support.
This language is supported by all these databases that handle multiple tables, that's why they are called **SQL Databases**. Although, each database has small variations in the SQL language they support (*dialect*).
Let's imagine that the table holding the heroes is called the `hero` table. An example of a SQL query to get all the data from it could look like:

View File

@@ -62,7 +62,7 @@ The user is probably, in some way, telling your application:
2
```
And the would be this table (with a single row):
And the result would be this table (with a single row):
<table>
<tr>
@@ -111,7 +111,7 @@ DROP TABLE hero;
That is how you tell the database in SQL to delete the entire table `hero`.
<a href="http://www.nooooooooooooooo.com/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
<a href="https://theuselessweb.site/nooooooooooooooo/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
### SQL Sanitization
@@ -143,7 +143,7 @@ If the user provides this ID:
2
```
...the would be this table (with a single row):
...the result would be this table (with a single row):
<table>
<tr>
@@ -305,4 +305,4 @@ You will see **your own code** a lot more than the internal table names, so it's
So, to keep things consistent, I'll keep using the same table names that **SQLModel** would have generated.
!!! tip
You can also override the table name. You can read about it in the Advanced User Guide.
You can also override the table name. You can read about it in the Advanced User Guide.

View File

@@ -12,7 +12,7 @@ Nevertheless, SQLModel is completely **independent** of FastAPI and can be used
## Just Modern Python
It's all based on standard <abbr title="Python currently supported versions, 3.6 and above.">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
It's all based on standard <abbr title="Currently supported versions of Python">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">Python types intro</a>.

View File

@@ -12,7 +12,7 @@ And there are several ways to get help too.
## Subscribe to the FastAPI and Friends newsletter
You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/newsletter/){.internal-link target=_blank} to stay updated about:
You can subscribe to the (infrequent) <a href="https://fastapi.tiangolo.com/newsletter" class="external-link" target="_blank">**FastAPI and friends** newsletter</a> to stay updated about:
* News about FastAPI and friends, including SQLModel 🚀
* Guides 📝
@@ -58,26 +58,125 @@ You can:
I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc.
## Help others with issues in GitHub
## Help others with questions in GitHub
You can see <a href="https://github.com/tiangolo/sqlmodel/issues" class="external-link" target="_blank">existing issues</a> and try and help others, most of the times they are questions that you might already know the answer for. 🤓
You can try and help others with their questions in:
* <a href="https://github.com/tiangolo/sqlmodel/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
* <a href="https://github.com/tiangolo/sqlmodel/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
In many cases you might already know the answer for those questions. 🤓
Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗
The idea is for the **SQLModel** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other.
---
Here's how to help others with questions (in discussions or issues):
### Understand the question
* Check if you can understand what is the **purpose** and use case of the person asking.
* Then check if the question (the vast majority are questions) is **clear**.
* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**.
* If you can't understand the question, ask for more **details**.
### Reproduce the problem
For most of the cases and most of the questions there's something related to the person's **original code**.
In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**.
* You can ask them to provide a <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">minimal, reproducible, example</a>, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better.
* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first.
### Suggest solutions
* After being able to understand the question, you can give them a possible **answer**.
* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do.
### Ask to close
If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸
* Now, if that solved their problem, you can ask them to:
* In GitHub Discussions: mark the comment as the **answer**.
* In GitHub Issues: **close** the issue**.
## Watch the GitHub repository
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue.
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc.
Then you can try and help them solve those issues.
Then you can try and help them solve those questions.
## Create issues
## Ask Questions
You can <a href="https://github.com/tiangolo/sqlmodel/issues/new/choose" class="external-link" target="_blank">create a new issue</a> in the GitHub repository, for example to:
You can <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">create a new question</a> in the GitHub repository, for example to:
* Ask a **question** or ask about a **problem**.
* Suggest a new **feature**.
**Note**: if you create an issue, then I'm going to ask you to also help others. 😉
**Note**: if you do it, then I'm going to ask you to also help others. 😉
## Review Pull Requests
You can help me review pull requests from others.
Again, please try your best to be kind. 🤗
---
Here's what to have in mind and how to review a pull request:
### Understand the problem
* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue.
* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that.
### Don't worry about style
* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually.
* Also don't worry about style rules, there are already automatized tools checking that.
And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes.
### Check the code
* Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem.
* Then **comment** saying that you did that, that's how I will know you really checked it.
!!! info
Unfortunately, I can't simply trust PRs that just have several approvals.
Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅
So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓
* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things.
### Tests
* Help me check that the PR has **tests**.
* Check that the tests **fail** before the PR. 🚨
* Then check that the tests **pass** after the PR. ✅
* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that.
* Then also comment what you tried, that way I'll know that you checked it. 🤓
## Create a Pull Request
@@ -86,7 +185,44 @@ You can [contribute](contributing.md){.internal-link target=_blank} to the sourc
* To fix a typo you found on the documentation.
* To propose new documentation sections.
* To fix an existing issue/bug.
* Make sure to add tests.
* To add a new feature.
* Make sure to add tests.
* Make sure to add documentation if it's relevant.
## Help Maintain SQLModel
Help me maintain **SQLModel**! 🤓
There's a lot of work to do, and for most of it, **YOU** can do it.
The main tasks that you can do right now are:
* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above).
* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above).
Those two tasks are what **consume time the most**. That's the main work of maintaining SQLModel.
If you can help me with that, **you are helping me maintain SQLModel** and making sure it keeps **advancing faster and better**. 🚀
## Join the chat
Join the 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">FastAPI and Friends Discord chat server</a> 👥 and hang out with others in the community. There's a `#sqlmodel` channel.
!!! tip
For questions, ask them in <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, there's a much better chance you will receive help there.
Use the chat only for other general conversations.
### Don't use the chat for questions
Have in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers.
In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat. 😅
Conversations in the chat are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation.
On the other side, there are thousands of users in the chat, so there's a high chance you'll find someone to talk to there, almost all the time. 😄
## Sponsor the author

View File

@@ -90,4 +90,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -202,4 +202,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -148,4 +148,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -81,4 +81,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -34,4 +34,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -61,4 +61,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,97 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1463" dy="1403" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="68" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="580" y="1030" width="40" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,97 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1707" dy="1637" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="68" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="580" y="1030" width="40" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,97 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1205" dy="1155" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="580" y="1030" width="560" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,100 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1463" dy="1403" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="68" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="300" y="1030" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="580" y="1030" width="560" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,97 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="300" y="1030" width="840" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,100 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="300" y="1030" width="840" height="80" as="geometry"/>
</mxCell>
<mxCell id="70" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="180" y="1030" width="40" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,100 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="300" y="1030" width="840" height="80" as="geometry"/>
</mxCell>
<mxCell id="71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="1030" width="120" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,103 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
</mxCell>
<mxCell id="39" value="&lt;font style=&quot;font-size: 24px&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="&lt;font style=&quot;font-size: 24px&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;font style=&quot;font-size: 24px&quot;&gt;C&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeWidth=3;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;font style=&quot;font-size: 24px&quot;&gt;E&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;font style=&quot;font-size: 24px&quot;&gt;F&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;font style=&quot;font-size: 24px&quot;&gt;G&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;H&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;I&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="48" value="&lt;font style=&quot;font-size: 24px&quot;&gt;J&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;K&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="50" value="&lt;font style=&quot;font-size: 24px&quot;&gt;L&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;M&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="52" value="&lt;font style=&quot;font-size: 24px&quot;&gt;N&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;font style=&quot;font-size: 24px&quot;&gt;O&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;font style=&quot;font-size: 24px&quot;&gt;P&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Q&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="&lt;font style=&quot;font-size: 24px&quot;&gt;R&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="57" value="&lt;font style=&quot;font-size: 24px&quot;&gt;S&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="58" value="&lt;font style=&quot;font-size: 24px&quot;&gt;T&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="59" value="&lt;font style=&quot;font-size: 24px&quot;&gt;U&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;V&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;W&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="62" value="&lt;font style=&quot;font-size: 24px&quot;&gt;X&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Y&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Z&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Dictionary&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="260" y="1030" width="880" height="80" as="geometry"/>
</mxCell>
<mxCell id="71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="100" y="1030" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="72" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeWidth=3;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="220" y="1030" width="40" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,92 @@
<mxfile host="65bd71144e">
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
<mxGraphModel dx="1707" dy="1637" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="80" y="420" width="1020" height="490" as="geometry"/>
</mxCell>
<mxCell id="66" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Technical Book&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="492.5" y="440" width="195" height="30" as="geometry"/>
</mxCell>
<mxCell id="77" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 1&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="100" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="83" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="240" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="84" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 3&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="380" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="85" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 4&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="520" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="86" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 5&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="660" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="87" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 6&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="800" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="88" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Chapter 7&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="940" y="490" width="140" height="90" as="geometry"/>
</mxCell>
<mxCell id="74" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="100" y="580" width="980" height="310" as="geometry"/>
</mxCell>
<mxCell id="75" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Book Index&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="498.125" y="840" width="183.75" height="30" as="geometry"/>
</mxCell>
<mxCell id="89" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="42" target="77">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="92" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="42" target="86">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="42" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Database&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
<mxGeometry x="119.99615384615385" y="690" width="150.76923076923077" height="70" as="geometry"/>
</mxCell>
<mxCell id="94" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="93" target="83">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="330" y="820"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="95" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="93" target="85">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="590" y="800"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="93" value="&lt;font style=&quot;font-size: 24px&quot;&gt;Python&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="909.9961538461539" y="780" width="150.76923076923077" height="70" as="geometry"/>
</mxCell>
<mxCell id="97" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="96" target="87">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="98" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="96" target="86">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="770" y="725"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="96" value="&lt;span style=&quot;font-size: 24px&quot;&gt;Files&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="909.9961538461539" y="690" width="150.76923076923077" height="70" as="geometry"/>
</mxCell>
<mxCell id="100" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="99" target="84">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="450" y="800"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="99" value="&lt;span style=&quot;font-size: 24px&quot;&gt;Editors&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" vertex="1" parent="1">
<mxGeometry x="119.9961538461539" y="780" width="150.76923076923077" height="70" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -217,4 +217,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -130,4 +130,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -130,4 +130,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -130,4 +130,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -38,4 +38,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -49,4 +49,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -139,4 +139,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -11,9 +11,8 @@
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
</a>
<a href="https://codecov.io/gh/tiangolo/sqlmodel" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/sqlmodel?color=%2334D058" alt="Coverage">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/sqlmodel" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/sqlmodel.svg" alt="Coverage">
<a href="https://pypi.org/project/sqlmodel" target="_blank">
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
@@ -51,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
## Requirements
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
@@ -69,7 +68,7 @@ Successfully installed sqlmodel
## Example
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com" target="_blank">SQLModel documentation</a>.
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com/databases/" target="_blank">SQLModel documentation</a>.
Here's a quick example. ✨
@@ -212,4 +211,4 @@ And at the same time, ✨ it is also a **Pydantic** model ✨. You can use inher
## License
This project is licensed under the terms of the MIT license.
This project is licensed under the terms of the [MIT license](https://github.com/tiangolo/sqlmodel/blob/main/LICENSE).

View File

@@ -72,14 +72,14 @@ class Termynal {
* Initialise the widget, get lines, clear container and start animation.
*/
init() {
/**
/**
* Calculates width and height of Termynal container.
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
*/
*/
const containerStyle = getComputedStyle(this.container);
this.container.style.width = containerStyle.width !== '0px' ?
this.container.style.width = containerStyle.width !== '0px' ?
containerStyle.width : undefined;
this.container.style.minHeight = containerStyle.height !== '0px' ?
this.container.style.minHeight = containerStyle.height !== '0px' ?
containerStyle.height : undefined;
this.container.setAttribute('data-termynal', '');
@@ -138,7 +138,7 @@ class Termynal {
restart.innerHTML = "restart ↻"
return restart
}
generateFinish() {
const finish = document.createElement('a')
finish.onclick = (e) => {
@@ -215,7 +215,7 @@ class Termynal {
/**
* Converts line data objects into line elements.
*
*
* @param {Object[]} lineData - Dynamically loaded lines.
* @param {Object} line - Line data object.
* @returns {Element[]} - Array of line elements.
@@ -231,7 +231,7 @@ class Termynal {
/**
* Helper function for generating attributes string.
*
*
* @param {Object} line - Line data object.
* @returns {string} - String of attributes.
*/

31
docs/overrides/main.html Normal file
View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{%- block scripts %}
{{ super() }}
<script src="https://cdn.jsdelivr.net/npm/qabot@0.4"></script>
<script>
// This prevents the global search from interfering with qa-bot's internal text input.
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('qa-bot').forEach((x) => {
x.addEventListener('keydown', (event) => {
event.stopPropagation();
});
});
});
</script>
<qa-bot
server="https://tiangolo-sqlmodel.docsqa.jina.ai"
theme="infer"
title="SQLModel Bot"
description="SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
style="font-size: 0.8rem"
>
<template>
<dl>
<dt>You can ask questions about SQLModel. Try:</dt>
<dd>Which Python version is supported?</dd>
<dd>How SQLModel interacts with the database?</dd>
<dd>How can I link tables?</dd>
</dl>
</template>
</qa-bot>
{%- endblock %}

View File

@@ -2,6 +2,271 @@
## Latest Changes
## 0.0.12
### Features
* ✨ Upgrade SQLAlchemy to 2.0. PR [#700](https://github.com/tiangolo/sqlmodel/pull/700) by [@tiangolo](https://github.com/tiangolo) including initial work in PR [#563](https://github.com/tiangolo/sqlmodel/pull/563) by [@farahats9](https://github.com/farahats9).
### Internal
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#686](https://github.com/tiangolo/sqlmodel/pull/686) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* 👷 Upgrade latest-changes GitHub Action. PR [#693](https://github.com/tiangolo/sqlmodel/pull/693) by [@tiangolo](https://github.com/tiangolo).
## 0.0.11
### Features
* ✨ Add support for passing a custom SQLAlchemy type to `Field()` with `sa_type`. PR [#505](https://github.com/tiangolo/sqlmodel/pull/505) by [@maru0123-2004](https://github.com/maru0123-2004).
* You might consider this a breaking change if you were using an incompatible combination of arguments, those arguments were not taking effect and now you will have a type error and runtime error telling you that.
* ✨ Do not allow invalid combinations of field parameters for columns and relationships, `sa_column` excludes `sa_column_args`, `primary_key`, `nullable`, etc. PR [#681](https://github.com/tiangolo/sqlmodel/pull/681) by [@tiangolo](https://github.com/tiangolo).
### Docs
* 🎨 Update inline source examples, hide `#` in annotations (from MkDocs Material). PR [#677](https://github.com/tiangolo/sqlmodel/pull/677) by [@Matthieu-LAURENT39](https://github.com/Matthieu-LAURENT39).
### Internal
* ⬆ Update coverage requirement from ^6.2 to >=6.2,<8.0. PR [#663](https://github.com/tiangolo/sqlmodel/pull/663) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update mkdocs-material requirement from 9.1.21 to 9.2.7. PR [#675](https://github.com/tiangolo/sqlmodel/pull/675) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆️ Upgrade mypy manually. PR [#684](https://github.com/tiangolo/sqlmodel/pull/684) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Update black requirement from ^22.10.0 to >=22.10,<24.0. PR [#664](https://github.com/tiangolo/sqlmodel/pull/664) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Update CI to build MkDocs Insiders only when the secrets are available, for Dependabot. PR [#683](https://github.com/tiangolo/sqlmodel/pull/683) by [@tiangolo](https://github.com/tiangolo).
## 0.0.10
### Features
* ✨ Add support for all `Field` parameters from Pydantic `1.9.0` and above, make Pydantic `1.9.0` the minimum required version. PR [#440](https://github.com/tiangolo/sqlmodel/pull/440) by [@daniil-berg](https://github.com/daniil-berg).
### Internal
* 🔧 Adopt Ruff for formatting. PR [#679](https://github.com/tiangolo/sqlmodel/pull/679) by [@tiangolo](https://github.com/tiangolo).
## 0.0.9
### Breaking Changes
* 🗑️ Deprecate Python 3.6 and upgrade Poetry and Poetry Version Plugin. PR [#627](https://github.com/tiangolo/sqlmodel/pull/627) by [@tiangolo](https://github.com/tiangolo).
### Features
* ✨ Raise a more clear error when a type is not valid. PR [#425](https://github.com/tiangolo/sqlmodel/pull/425) by [@ddanier](https://github.com/ddanier).
### Fixes
* 🐛 Fix `AsyncSession` type annotations for `exec()`. PR [#58](https://github.com/tiangolo/sqlmodel/pull/58) by [@Bobronium](https://github.com/Bobronium).
* 🐛 Fix allowing using a `ForeignKey` directly, remove repeated column construction from `SQLModelMetaclass.__init__` and upgrade minimum SQLAlchemy to `>=1.4.36`. PR [#443](https://github.com/tiangolo/sqlmodel/pull/443) by [@daniil-berg](https://github.com/daniil-berg).
* 🐛 Fix enum type checks ordering in `get_sqlalchemy_type`. PR [#669](https://github.com/tiangolo/sqlmodel/pull/669) by [@tiangolo](https://github.com/tiangolo).
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#461](https://github.com/tiangolo/sqlmodel/pull/461) by [@byrman](https://github.com/byrman).
### Upgrades
* ⬆️ Upgrade support for SQLAlchemy 1.4.49, update tests. PR [#519](https://github.com/tiangolo/sqlmodel/pull/519) by [@sandrotosi](https://github.com/sandrotosi).
* ⬆ Raise SQLAlchemy version requirement to at least `1.4.29` (related to #434). PR [#439](https://github.com/tiangolo/sqlmodel/pull/439) by [@daniil-berg](https://github.com/daniil-berg).
### Docs
* 📝 Clarify description of in-memory SQLite database in `docs/tutorial/create-db-and-table.md`. PR [#601](https://github.com/tiangolo/sqlmodel/pull/601) by [@SimonCW](https://github.com/SimonCW).
* 📝 Tweak wording in `docs/tutorial/fastapi/multiple-models.md`. PR [#674](https://github.com/tiangolo/sqlmodel/pull/674) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix contributing instructions to run tests, update script name. PR [#634](https://github.com/tiangolo/sqlmodel/pull/634) by [@PookieBuns](https://github.com/PookieBuns).
* 📝 Update link to docs for intro to databases. PR [#593](https://github.com/tiangolo/sqlmodel/pull/593) by [@abenezerBelachew](https://github.com/abenezerBelachew).
* 📝 Update docs, use `offset` in example with `limit` and `where`. PR [#273](https://github.com/tiangolo/sqlmodel/pull/273) by [@jbmchuck](https://github.com/jbmchuck).
* 📝 Fix docs for Pydantic's fields using `le` (`lte` is invalid, use `le` ). PR [#207](https://github.com/tiangolo/sqlmodel/pull/207) by [@jrycw](https://github.com/jrycw).
* 📝 Update outdated link in `docs/db-to-code.md`. PR [#649](https://github.com/tiangolo/sqlmodel/pull/649) by [@MatveyF](https://github.com/MatveyF).
* ✏️ Fix typos found with codespell. PR [#520](https://github.com/tiangolo/sqlmodel/pull/520) by [@kianmeng](https://github.com/kianmeng).
* 📝 Fix typos (duplication) in main page. PR [#631](https://github.com/tiangolo/sqlmodel/pull/631) by [@Mr-DRP](https://github.com/Mr-DRP).
* 📝 Update release notes, add second author to PR. PR [#429](https://github.com/tiangolo/sqlmodel/pull/429) by [@br-follow](https://github.com/br-follow).
* 📝 Update instructions about how to make a foreign key required in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#474](https://github.com/tiangolo/sqlmodel/pull/474) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* 📝 Update help SQLModel docs. PR [#548](https://github.com/tiangolo/sqlmodel/pull/548) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in internal function name `get_sqlachemy_type()`. PR [#496](https://github.com/tiangolo/sqlmodel/pull/496) by [@cmarqu](https://github.com/cmarqu).
* ✏️ Fix typo in docs. PR [#446](https://github.com/tiangolo/sqlmodel/pull/446) by [@davidbrochart](https://github.com/davidbrochart).
* ✏️ Fix typo in `docs/tutorial/create-db-and-table.md`. PR [#477](https://github.com/tiangolo/sqlmodel/pull/477) by [@FluffyDietEngine](https://github.com/FluffyDietEngine).
* ✏️ Fix small typos in docs. PR [#481](https://github.com/tiangolo/sqlmodel/pull/481) by [@micuffaro](https://github.com/micuffaro).
### Internal
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#672](https://github.com/tiangolo/sqlmodel/pull/672) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.28.0. PR [#660](https://github.com/tiangolo/sqlmodel/pull/660) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ✅ Refactor OpenAPI FastAPI tests to simplify updating them later, this moves things around without changes. PR [#671](https://github.com/tiangolo/sqlmodel/pull/671) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/checkout from 3 to 4. PR [#670](https://github.com/tiangolo/sqlmodel/pull/670) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 🔧 Update mypy config, use `strict = true` instead of manual configs. PR [#428](https://github.com/tiangolo/sqlmodel/pull/428) by [@michaeloliverx](https://github.com/michaeloliverx).
* ⬆️ Upgrade MkDocs Material. PR [#668](https://github.com/tiangolo/sqlmodel/pull/668) by [@tiangolo](https://github.com/tiangolo).
* 🎨 Update docs format and references with pre-commit and Ruff. PR [#667](https://github.com/tiangolo/sqlmodel/pull/667) by [@tiangolo](https://github.com/tiangolo).
* 🎨 Run pre-commit on all files and autoformat. PR [#666](https://github.com/tiangolo/sqlmodel/pull/666) by [@tiangolo](https://github.com/tiangolo).
* 👷 Move to Ruff and add pre-commit. PR [#661](https://github.com/tiangolo/sqlmodel/pull/661) by [@tiangolo](https://github.com/tiangolo).
* 🛠️ Add `CITATION.cff` file for academic citations. PR [#13](https://github.com/tiangolo/sqlmodel/pull/13) by [@sugatoray](https://github.com/sugatoray).
* 👷 Update docs deployments to Cloudflare. PR [#630](https://github.com/tiangolo/sqlmodel/pull/630) by [@tiangolo](https://github.com/tiangolo).
* 👷‍♂️ Upgrade CI for docs. PR [#628](https://github.com/tiangolo/sqlmodel/pull/628) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update CI debug mode with Tmate. PR [#629](https://github.com/tiangolo/sqlmodel/pull/629) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update latest changes token. PR [#616](https://github.com/tiangolo/sqlmodel/pull/616) by [@tiangolo](https://github.com/tiangolo).
* ⬆️ Upgrade analytics. PR [#558](https://github.com/tiangolo/sqlmodel/pull/558) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update new issue chooser to point to GitHub Discussions. PR [#546](https://github.com/tiangolo/sqlmodel/pull/546) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Add template for GitHub Discussion questions and update issues template. PR [#544](https://github.com/tiangolo/sqlmodel/pull/544) by [@tiangolo](https://github.com/tiangolo).
* 👷 Refactor CI artifact upload/download for docs previews. PR [#514](https://github.com/tiangolo/sqlmodel/pull/514) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/cache from 2 to 3. PR [#497](https://github.com/tiangolo/sqlmodel/pull/497) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.2. PR [#493](https://github.com/tiangolo/sqlmodel/pull/493) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 🔧 Update Smokeshow coverage threshold. PR [#487](https://github.com/tiangolo/sqlmodel/pull/487) by [@tiangolo](https://github.com/tiangolo).
* 👷 Move from Codecov to Smokeshow. PR [#486](https://github.com/tiangolo/sqlmodel/pull/486) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/setup-python from 2 to 4. PR [#411](https://github.com/tiangolo/sqlmodel/pull/411) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update black requirement from ^21.5-beta.1 to ^22.10.0. PR [#460](https://github.com/tiangolo/sqlmodel/pull/460) by [@dependabot[bot]](https://github.com/apps/dependabot).
* Add extra dev dependencies for MkDocs Material. PR [#485](https://github.com/tiangolo/sqlmodel/pull/485) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Update mypy requirement from 0.930 to 0.971. PR [#380](https://github.com/tiangolo/sqlmodel/pull/380) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update coverage requirement from ^5.5 to ^6.2. PR [#171](https://github.com/tiangolo/sqlmodel/pull/171) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump codecov/codecov-action from 2 to 3. PR [#415](https://github.com/tiangolo/sqlmodel/pull/415) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump actions/upload-artifact from 2 to 3. PR [#412](https://github.com/tiangolo/sqlmodel/pull/412) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update flake8 requirement from ^3.9.2 to ^5.0.4. PR [#396](https://github.com/tiangolo/sqlmodel/pull/396) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update pytest requirement from ^6.2.4 to ^7.0.1. PR [#242](https://github.com/tiangolo/sqlmodel/pull/242) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump actions/checkout from 2 to 3.1.0. PR [#458](https://github.com/tiangolo/sqlmodel/pull/458) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump dawidd6/action-download-artifact from 2.9.0 to 2.24.0. PR [#470](https://github.com/tiangolo/sqlmodel/pull/470) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Update Dependabot config. PR [#484](https://github.com/tiangolo/sqlmodel/pull/484) by [@tiangolo](https://github.com/tiangolo).
## 0.0.8
### Fixes
* 🐛 Fix auto detecting and setting `nullable`, allowing overrides in field. PR [#423](https://github.com/tiangolo/sqlmodel/pull/423) by [@JonasKs](https://github.com/JonasKs) and [@br-follow](https://github.com/br-follow).
* ♻️ Update `expresion.py`, sync from Jinja2 template, implement `inherit_cache` to solve errors like: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#422](https://github.com/tiangolo/sqlmodel/pull/422) by [@tiangolo](https://github.com/tiangolo).
### Docs
* 📝 Adjust and clarify docs for `docs/tutorial/create-db-and-table.md`. PR [#426](https://github.com/tiangolo/sqlmodel/pull/426) by [@tiangolo](https://github.com/tiangolo).
* ✏ Fix typo in `docs/tutorial/connect/remove-data-connections.md`. PR [#421](https://github.com/tiangolo/sqlmodel/pull/421) by [@VerdantFox](https://github.com/VerdantFox).
## 0.0.7
### Features
* ✨ Allow setting `unique` in `Field()` for a column. PR [#83](https://github.com/tiangolo/sqlmodel/pull/83) by [@raphaelgibson](https://github.com/raphaelgibson).
* ✨ Update GUID handling to use stdlib `UUID.hex` instead of an `int`. PR [#26](https://github.com/tiangolo/sqlmodel/pull/26) by [@andrewbolster](https://github.com/andrewbolster).
* ✨ Raise an exception when using a Pydantic field type with no matching SQLAlchemy type. PR [#18](https://github.com/tiangolo/sqlmodel/pull/18) by [@elben10](https://github.com/elben10).
* ⬆ Upgrade constrain for SQLAlchemy = ">=1.4.17,<=1.4.41". PR [#371](https://github.com/tiangolo/sqlmodel/pull/371) by [@RobertRosca](https://github.com/RobertRosca).
* ✨ Add new `Session.get()` parameter `execution_options`. PR [#302](https://github.com/tiangolo/sqlmodel/pull/302) by [@tiangolo](https://github.com/tiangolo).
### Fixes
* 🐛 Fix type annotations for `Model.parse_obj()`, and `Model.validate()`. PR [#321](https://github.com/tiangolo/sqlmodel/pull/321) by [@phi-friday](https://github.com/phi-friday).
* 🐛 Fix `Select` and `SelectOfScalar` to inherit cache to avoid warning: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#234](https://github.com/tiangolo/sqlmodel/pull/234) by [@rabinadk1](https://github.com/rabinadk1).
* 🐛 Fix handling validators for non-default values. PR [#253](https://github.com/tiangolo/sqlmodel/pull/253) by [@byrman](https://github.com/byrman).
* 🐛 Fix fields marked as "set" in models. PR [#117](https://github.com/tiangolo/sqlmodel/pull/117) by [@statt8900](https://github.com/statt8900).
* 🐛 Fix Enum handling in SQLAlchemy. PR [#165](https://github.com/tiangolo/sqlmodel/pull/165) by [@chriswhite199](https://github.com/chriswhite199).
* 🐛 Fix setting nullable property of Fields that don't accept `None`. PR [#79](https://github.com/tiangolo/sqlmodel/pull/79) by [@van51](https://github.com/van51).
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#322](https://github.com/tiangolo/sqlmodel/pull/322) by [@byrman](https://github.com/byrman).
### Docs
* 📝 Update docs for models for updating, `id` should not be updatable. PR [#335](https://github.com/tiangolo/sqlmodel/pull/335) by [@kurtportelli](https://github.com/kurtportelli).
* ✏ Fix broken variable/typo in docs for Read Relationships, `hero_spider_boy.id` => `hero_spider_boy.team_id`. PR [#106](https://github.com/tiangolo/sqlmodel/pull/106) by [@yoannmos](https://github.com/yoannmos).
* 🎨 Remove unwanted highlight in the docs. PR [#233](https://github.com/tiangolo/sqlmodel/pull/233) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* ✏ Fix typos in `docs/databases.md` and `docs/tutorial/index.md`. PR [#35](https://github.com/tiangolo/sqlmodel/pull/35) by [@prrao87](https://github.com/prrao87).
* ✏ Fix typo in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#239](https://github.com/tiangolo/sqlmodel/pull/239) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* ✏ Fix typo in `docs/tutorial/fastapi/simple-hero-api.md`. PR [#80](https://github.com/tiangolo/sqlmodel/pull/80) by [@joemudryk](https://github.com/joemudryk).
* ✏ Fix typos in multiple files in the docs. PR [#400](https://github.com/tiangolo/sqlmodel/pull/400) by [@VictorGambarini](https://github.com/VictorGambarini).
* ✏ Fix typo in `docs/tutorial/code-structure.md`. PR [#344](https://github.com/tiangolo/sqlmodel/pull/344) by [@marciomazza](https://github.com/marciomazza).
* ✏ Fix typo in `docs/db-to-code.md`. PR [#155](https://github.com/tiangolo/sqlmodel/pull/155) by [@gr8jam](https://github.com/gr8jam).
* ✏ Fix typo in `docs/contributing.md`. PR [#323](https://github.com/tiangolo/sqlmodel/pull/323) by [@Fardad13](https://github.com/Fardad13).
* ✏ Fix typo in `docs/tutorial/fastapi/tests.md`. PR [#265](https://github.com/tiangolo/sqlmodel/pull/265) by [@johnhoman](https://github.com/johnhoman).
* ✏ Fix typo in `docs/tutorial/where.md`. PR [#286](https://github.com/tiangolo/sqlmodel/pull/286) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* ✏ Fix typos in `docs/tutorial/fastapi/update.md`. PR [#268](https://github.com/tiangolo/sqlmodel/pull/268) by [@cirrusj](https://github.com/cirrusj).
* ✏ Fix typo in `docs/tutorial/fastapi/simple-hero-api.md`. PR [#247](https://github.com/tiangolo/sqlmodel/pull/247) by [@hao-wang](https://github.com/hao-wang).
* ✏ Fix typos in `docs/tutorial/automatic-id-none-refresh.md`, `docs/tutorial/fastapi/update.md`, `docs/tutorial/select.md`. PR [#185](https://github.com/tiangolo/sqlmodel/pull/185) by [@rootux](https://github.com/rootux).
* ✏ Fix typo in `docs/databases.md`. PR [#177](https://github.com/tiangolo/sqlmodel/pull/177) by [@seandlg](https://github.com/seandlg).
* ✏ Fix typos in `docs/tutorial/fastapi/update.md`. PR [#162](https://github.com/tiangolo/sqlmodel/pull/162) by [@wmcgee3](https://github.com/wmcgee3).
* ✏ Fix typos in `docs/tutorial/code-structure.md`, `docs/tutorial/fastapi/multiple-models.md`, `docs/tutorial/fastapi/simple-hero-api.md`, `docs/tutorial/many-to-many/index.md`. PR [#116](https://github.com/tiangolo/sqlmodel/pull/116) by [@moonso](https://github.com/moonso).
* ✏ Fix typo in `docs/tutorial/fastapi/teams.md`. PR [#154](https://github.com/tiangolo/sqlmodel/pull/154) by [@chrisgoddard](https://github.com/chrisgoddard).
* ✏ Fix typo variable in example about relationships and `back_populates`, always use `hero` instead of `owner`. PR [#120](https://github.com/tiangolo/sqlmodel/pull/120) by [@onionj](https://github.com/onionj).
* ✏ Fix typo in `docs/tutorial/fastapi/tests.md`. PR [#113](https://github.com/tiangolo/sqlmodel/pull/113) by [@feanil](https://github.com/feanil).
* ✏ Fix typo in `docs/tutorial/where.md`. PR [#72](https://github.com/tiangolo/sqlmodel/pull/72) by [@ZettZet](https://github.com/ZettZet).
* ✏ Fix typo in `docs/tutorial/code-structure.md`. PR [#91](https://github.com/tiangolo/sqlmodel/pull/91) by [@dhiraj](https://github.com/dhiraj).
* ✏ Fix broken link to newsletter sign-up in `docs/help.md`. PR [#84](https://github.com/tiangolo/sqlmodel/pull/84) by [@mborus](https://github.com/mborus).
* ✏ Fix typos in `docs/tutorial/many-to-many/create-models-with-link.md`. PR [#45](https://github.com/tiangolo/sqlmodel/pull/45) by [@xginn8](https://github.com/xginn8).
* ✏ Fix typo in `docs/tutorial/index.md`. PR [#398](https://github.com/tiangolo/sqlmodel/pull/398) by [@ryangrose](https://github.com/ryangrose).
### Internal
* ♻ Refactor internal statements to simplify code. PR [#53](https://github.com/tiangolo/sqlmodel/pull/53) by [@yezz123](https://github.com/yezz123).
* ♻ Refactor internal imports to reduce redundancy. PR [#272](https://github.com/tiangolo/sqlmodel/pull/272) by [@aminalaee](https://github.com/aminalaee).
* ⬆ Update development requirement for FastAPI from `^0.68.0` to `^0.68.1`. PR [#48](https://github.com/tiangolo/sqlmodel/pull/48) by [@alucarddelta](https://github.com/alucarddelta).
* ⏪ Revert upgrade Poetry, to make a release that supports Python 3.6 first. PR [#417](https://github.com/tiangolo/sqlmodel/pull/417) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add dependabot for GitHub Actions. PR [#410](https://github.com/tiangolo/sqlmodel/pull/410) by [@tiangolo](https://github.com/tiangolo).
* ⬆️ Upgrade Poetry to version `==1.2.0b1`. PR [#303](https://github.com/tiangolo/sqlmodel/pull/303) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add CI for Python 3.10. PR [#305](https://github.com/tiangolo/sqlmodel/pull/305) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#263](https://github.com/tiangolo/sqlmodel/pull/263) by [@tiangolo](https://github.com/tiangolo).
* 👷 Upgrade Codecov GitHub Action. PR [#304](https://github.com/tiangolo/sqlmodel/pull/304) by [@tiangolo](https://github.com/tiangolo).
* 💚 Only run CI on push when on master, to avoid duplicate runs on PRs. PR [#244](https://github.com/tiangolo/sqlmodel/pull/244) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Upgrade MkDocs Material and update configs. PR [#217](https://github.com/tiangolo/sqlmodel/pull/217) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Upgrade mypy, fix type annotations. PR [#218](https://github.com/tiangolo/sqlmodel/pull/218) by [@tiangolo](https://github.com/tiangolo).
## 0.0.6
### Breaking Changes
**SQLModel** no longer creates indexes by default for every column, indexes are now opt-in. You can read more about it in PR [#205](https://github.com/tiangolo/sqlmodel/pull/205).
Before this change, if you had a model like this:
```Python
from typing import Optional
from sqlmodel import Field, SQLModel
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
```
...when creating the tables, SQLModel version `0.0.5` and below, would also create an index for `name`, one for `secret_name`, and one for `age` (`id` is the primary key, so it doesn't need an additional index).
If you depended on having an index for each one of those columns, now you can (and would have to) define them explicitly:
```Python
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str = Field(index=True)
age: Optional[int] = Field(default=None, index=True)
```
There's a high chance you don't need indexes for all the columns. For example, you might only need indexes for `name` and `age`, but not for `secret_name`. In that case, you could define the model as:
```Python
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
```
If you already created your database tables with SQLModel using versions `0.0.5` or below, it would have also created those indexes in the database. In that case, you might want to manually drop (remove) some of those indexes, if they are unnecessary, to avoid the extra cost in performance and space.
Depending on the database you are using, there will be a different way to find the available indexes.
For example, let's say you no longer need the index for `secret_name`. You could check the current indexes in the database and find the one for `secret_name`, it could be named `ix_hero_secret_name`. Then you can remove it with SQL:
```SQL
DROP INDEX ix_hero_secret_name
```
or
```SQL
DROP INDEX ix_hero_secret_name ON hero;
```
Here's the new, extensive documentation explaining indexes and how to use them: [Indexes - Optimize Queries](https://sqlmodel.tiangolo.com/tutorial/indexes/).
### Docs
* ✨ Document indexes and make them opt-in. Here's the new documentation: [Indexes - Optimize Queries](https://sqlmodel.tiangolo.com/tutorial/indexes/). This is the same change described above in **Breaking Changes**. PR [#205](https://github.com/tiangolo/sqlmodel/pull/205) by [@tiangolo](https://github.com/tiangolo).
* ✏ Fix typo in FastAPI tutorial. PR [#192](https://github.com/tiangolo/sqlmodel/pull/192) by [@yaquelinehoyos](https://github.com/yaquelinehoyos).
* 📝 Add links to the license file. PR [#29](https://github.com/tiangolo/sqlmodel/pull/29) by [@sobolevn](https://github.com/sobolevn).
* ✏ Fix typos in docs titles. PR [#28](https://github.com/tiangolo/sqlmodel/pull/28) by [@Batalex](https://github.com/Batalex).
* ✏ Fix multiple typos and some rewording. PR [#22](https://github.com/tiangolo/sqlmodel/pull/22) by [@egrim](https://github.com/egrim).
* ✏ Fix typo in `docs/tutorial/automatic-id-none-refresh.md`. PR [#14](https://github.com/tiangolo/sqlmodel/pull/14) by [@leynier](https://github.com/leynier).
* ✏ Fix typos in `docs/tutorial/index.md` and `docs/databases.md`. PR [#5](https://github.com/tiangolo/sqlmodel/pull/5) by [@sebastianmarines](https://github.com/sebastianmarines).
## 0.0.5

View File

@@ -1,6 +1,6 @@
# Automatic IDs, None Defaults, and Refreshing Data
In the previous chapter we saw how to add rows to the database using **SQLModel**.
In the previous chapter, we saw how to add rows to the database using **SQLModel**.
Now let's talk a bit about why the `id` field **can't be `NULL`** on the database because it's a **primary key**, and we declare it using `Field(primary_key=True)`.
@@ -11,7 +11,7 @@ But the same `id` field actually **can be `None`** in the Python code, so we dec
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:6-10]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -36,7 +36,7 @@ When we create a new `Hero` instance, we don't set the `id`:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-26]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -68,7 +68,7 @@ If we ran this code before saving the hero to the database and the `hero_1.id` w
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
```
But by declaring it with `Optional[int]` the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
But by declaring it with `Optional[int]`, the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
## Print the Default `id` Values
@@ -79,7 +79,7 @@ We can confirm that by printing our heroes before adding them to the database:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-31]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -98,7 +98,7 @@ That will output:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
Before interacting with the database
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
@@ -118,14 +118,14 @@ What happens when we `add` these objects to the **session**?
After we add the `Hero` instance objects to the **session**, the IDs are *still* `None`.
We can verify by creating a session using a `with` block, and adding the objects. And then printing them again:
We can verify by creating a session using a `with` block and adding the objects. And then printing them again:
```Python hl_lines="19-21"
# Code above omitted 👆
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-41]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -144,7 +144,7 @@ This will, again, output the `id`s of the objects as `None`:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
After adding to the session
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
@@ -165,7 +165,7 @@ Then we can `commit` the changes in the session, and print again:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-48]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -184,7 +184,7 @@ And now, something unexpected happens, look at the output, it seems as if the `H
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// Here the engine talks to the database, the SQL part
INFO Engine BEGIN (implicit)
@@ -198,9 +198,9 @@ INFO Engine COMMIT
// And now our prints
After committing the session
Hero 1:
Hero 2:
Hero 3:
Hero 1:
Hero 2:
Hero 3:
// What is happening here? 😱
```
@@ -238,7 +238,7 @@ To confirm and understand how this **automatic expiration and refresh** of data
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-58]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -271,21 +271,21 @@ Let's see how it works:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// After committing, the objects are expired and have no values
After committing the session
Hero 1:
Hero 2:
Hero 3:
Hero 1:
Hero 2:
Hero 3:
// Now we will access an attribute like the ID, this is the first print
After committing the session, show IDs
// Notice that before printing the first ID, the Session makes the Engine go to the database to refresh the data 🤓
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00017s] (1,)
@@ -293,8 +293,8 @@ INFO Engine [generated in 0.00017s] (1,)
Hero 1 ID: 1
// Before the next print, refresh the data for the second object
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001245s ago] (2,)
@@ -302,8 +302,8 @@ INFO Engine [cached since 0.001245s ago] (2,)
Hero 2 ID: 2
// Before the third print, refresh its data
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002215s ago] (3,)
@@ -335,7 +335,7 @@ You can do that too with `session.refresh(object)`:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-67]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -362,23 +362,23 @@ Here's how the output would look like:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// The first refresh
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00024s] (1,)
// The second refresh
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001487s ago] (2,)
// The third refresh
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002377s ago] (3,)
@@ -399,7 +399,7 @@ In this case, after committing the object to the database with the **session**,
## Print Data After Closing the Session
Now, as a fnal experiment, we can also print data after the **session** is closed.
Now, as a final experiment, we can also print data after the **session** is closed.
There are no surprises here, it still works:
@@ -427,7 +427,7 @@ And the output shows again the same data:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// By finishing the with block, the Session is closed, including a rollback of any pending transaction that could have been there and was not committed
INFO Engine ROLLBACK
@@ -450,7 +450,7 @@ Now let's review all this code once again.
And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step.
```{ .python .annotate hl_lines="54" }
```{ .python .annotate }
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!}
```
@@ -468,12 +468,12 @@ INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
@@ -497,23 +497,23 @@ INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
INFO Engine [cached since 0.001483s ago] ('Rusty-Man', 'Tommy Sharp', 48)
INFO Engine COMMIT
After committing the session
Hero 1:
Hero 2:
Hero 3:
Hero 1:
Hero 2:
Hero 3:
After committing the session, show IDs
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00029s] (1,)
Hero 1 ID: 1
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002132s ago] (2,)
Hero 2 ID: 2
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.003367s ago] (3,)
Hero 3 ID: 3
@@ -521,16 +521,16 @@ After committing the session, show names
Hero 1 name: Deadpond
Hero 2 name: Spider-Boy
Hero 3 name: Rusty-Man
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00025s] (1,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001583s ago] (2,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002722s ago] (3,)
After refreshing the heroes

View File

@@ -8,7 +8,7 @@ The class `Hero` has a reference to the class `Team` internally.
But the class `Team` also has a reference to the class `Hero`.
So, if those two classes where in separate files and you tried to import the classes in each other's file directly, it would result in a **circular import**. 🔄
So, if those two classes were in separate files and you tried to import the classes in each other's file directly, it would result in a **circular import**. 🔄
And Python will not be able to handle it and will throw an error. 🚨
@@ -168,9 +168,9 @@ Let's assume that now the file structure is:
### Circular Imports and Type Annotations
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as oposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as opposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
but when using Python **type annotations** it's very common to need to declare the type of some variables with classes imported from other files.
But when using Python **type annotations** it's very common to need to declare the type of some variables with classes imported from other files.
And the files with those classes might **also need to import** more things from the first files.
@@ -198,7 +198,7 @@ It has a value of `True` for editors and tools that analyze the code with the ty
But when Python is executing, its value is `False`.
So, we can us it in an `if` block and import things inside the `if` block. And they will be "imported" only for editors, but not at runtime.
So, we can use it in an `if` block and import things inside the `if` block. And they will be "imported" only for editors, but not at runtime.
### Hero Model File

View File

@@ -159,7 +159,7 @@ As the `Hero` class model now has a field (column, attribute) `team_id`, we can
We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to.
If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`.
If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`.
But once the team is created and committed to the database, we can access the object's `id` field to get that ID.
@@ -171,8 +171,8 @@ That line alone would generate an output of:
```
INFO Engine BEGIN (implicit)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00025s] (2,)
```
@@ -199,8 +199,8 @@ Let's now create two more heroes:
When creating `hero_rusty_man`, we are accessing `team_preventers.id`, so that will also trigger a refresh of its data, generating an output of:
```
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.001795s ago] (1,)
```
@@ -256,18 +256,18 @@ $ python app.py
INFO Engine BEGIN (implicit)
// Refresh the first hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00021s] (1,)
// Refresh the second hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001575s ago] (2,)
// Refresh the third hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002518s ago] (3,)

View File

@@ -78,6 +78,7 @@ The `Team` model will be in a table automatically named `"team"`, and it will ha
* `id`, the primary key, automatically generated by the database
* `name`, the name of the team
* We also tell **SQLModel** to create an index for this column
* `headquarters`, the headquarters of the team
And finally we mark it as a table in the config.
@@ -105,9 +106,9 @@ This is the same model we have been using up to now, we are just adding the new
Most of that should look familiar:
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), becase there could be some heroes that don't belong to any team.
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), because there could be some heroes that don't belong to any team.
As we don't have to explicitly pass `team_id=None` when creating a hero, we add a default of `None` to the `Field()`.
We add a default of `None` to the `Field()` so we don't have to explicitly pass `team_id=None` when creating a hero.
Now, here's the new part:
@@ -190,24 +191,24 @@ INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
// Create the tables
INFO Engine
INFO Engine
CREATE TABLE team (
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00010s] ()
INFO Engine
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
@@ -228,9 +229,9 @@ So, the first SQL could also be written as:
```SQL
CREATE TABLE team (
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
PRIMARY KEY (id)
)
```
@@ -239,12 +240,12 @@ And the second table could be written as:
```SQL hl_lines="8"
CREATE TABLE hero (
id INTEGER,
name TEXT NOT NULL,
secret_name TEXT NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
id INTEGER,
name TEXT NOT NULL,
secret_name TEXT NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
```

View File

@@ -203,8 +203,8 @@ $ python app.py
// Previous output omitted 😉
// Get the heroes with their teams
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero, team
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero, team
WHERE hero.team_id = team.id
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] ()
@@ -323,7 +323,7 @@ $ python app.py
// Previous output omitted 😉
// Select using a JOIN with automatic ON
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero JOIN team ON team.id = hero.team_id
INFO Engine [no key 0.00032s] ()
@@ -458,7 +458,7 @@ $ python app.py
// Previous output omitted 😉
// SELECT using LEFT OUTER JOIN
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero LEFT OUTER JOIN team ON team.id = hero.team_id
INFO Engine [no key 0.00051s] ()
@@ -522,9 +522,9 @@ If we run that, it would output:
$ python app.py
// Select only the hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
// But still join with the team table
FROM hero JOIN team ON team.id = hero.team_id
FROM hero JOIN team ON team.id = hero.team_id
// And filter with WHERE to get only the Preventers
WHERE team.name = ?
INFO Engine [no key 0.00066s] ('Preventers',)
@@ -564,9 +564,9 @@ And if we run that, it will output:
$ python app.py
// Select the hero and the team data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
// Join the hero with the team table
FROM hero JOIN team ON team.id = hero.team_id
FROM hero JOIN team ON team.id = hero.team_id
// Filter with WHERE to get only Preventers
WHERE team.name = ?
INFO Engine [no key 0.00018s] ('Preventers',)

View File

@@ -46,7 +46,7 @@ We will continue with the code from the previous chapter.
## Break a Connection
We don't really have to delete anyting to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
We don't really have to delete anything to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
Let's say **Spider-Boy** is tired of the lack of friendly neighbors and wants to get out of the **Preventers**.
@@ -56,7 +56,7 @@ We can simply set the `team_id` to `None`, and now it doesn't have a connection
# Code above omitted 👆
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:31-32]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:68-72]!}
@@ -94,8 +94,8 @@ INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.1661s ago] (3,)

View File

@@ -56,7 +56,7 @@ Doing it is just like updating any other field:
# Code above omitted 👆
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:31-32]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:62-66]!}
@@ -94,8 +94,8 @@ INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.08837s ago] (3,)

View File

@@ -164,6 +164,6 @@ Of course, you can also go and take a full SQL course or read a book about SQL,
We saw how to interact with SQLite databases in files using **DB Browser for SQLite** in a visual user interface.
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correclty, to debug, etc.
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correctly, to debug, etc.
In the next chapters we will start using **SQLModel** to interact with the database, and we will continue to use **DB Browser for SQLite** at the same time to look at the database underneath. 🔍

View File

@@ -220,7 +220,7 @@ Each supported database has it's own URL type. For example, for **SQLite** it is
* `sqlite:///databases/local/application.db`
* `sqlite:///db.sqlite`
For SQLAlchemy, there's also a special one, which is a database all *in memory*, this means that it is deleted after the program terminates, and it's also very fast:
SQLite supports a special database that lives all *in memory*. Hence, it's very fast, but be careful, the database gets deleted after the program terminates. You can specify this in-memory database by using just two slash characters (`//`) and no file name:
* `sqlite://`
@@ -415,22 +415,22 @@ Now run the program with Python:
// We set echo=True, so this will show the SQL code
$ python app.py
// First, some boilerplate SQL that we are not that intereted in
// First, some boilerplate SQL that we are not that interested in
INFO Engine BEGIN (implicit)
INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine
INFO Engine
// Finally, the glorious SQL to create the table ✨
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
@@ -498,7 +498,7 @@ In this example it's just the `SQLModel.metadata.create_all(engine)`.
Let's put it in a function `create_db_and_tables()`:
```Python hl_lines="22-23"
```Python hl_lines="19-20"
{!./docs_src/tutorial/create_db_and_table/tutorial002.py[ln:1-20]!}
# More code here later 👇
@@ -513,9 +513,9 @@ Let's put it in a function `create_db_and_tables()`:
</details>
If `SQLModel.metadata.create_all(engine)` was not in a function and we tried to import something from this module (from this file) in another, it would try to create the database and table **every time**.
If `SQLModel.metadata.create_all(engine)` was not in a function and we tried to import something from this module (from this file) in another, it would try to create the database and table **every time** we executed that other file that imported this module.
We don't want that to happen like that, only when we **intend** it to happen, that's why we put it in a function.
We don't want that to happen like that, only when we **intend** it to happen, that's why we put it in a function, because we can make sure that the tables are created only when we call that function, and not when this module is imported somewhere else.
Now we would be able to, for example, import the `Hero` class in some other file without having those **side effects**.

View File

@@ -108,8 +108,8 @@ $ python app.py
// The SELECT with WHERE
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00011s] ('Spider-Youngster',)
@@ -272,8 +272,8 @@ $ python app.py
INFO Engine BEGIN (implicit)
// SQL to search for the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00013s] ('Spider-Youngster',)
```

View File

@@ -39,6 +39,6 @@ After deleting it successfully, we just return a response of:
## Recap
That's it, feel free to try it out in the interactve docs UI to delete some heroes. 💥
That's it, feel free to try it out in the interactive docs UI to delete some heroes. 💥
Using **FastAPI** to read data and combining it with **SQLModel** makes it quite straightforward to delete data from the database.

View File

@@ -1,15 +1,15 @@
# Read Heroes with Limit and Offset wtih FastAPI
# Read Heroes with Limit and Offset with FastAPI
When a client sends a request to get all the heroes, we have been returning them all.
But if we had **thousands** of heroes that could consume a lot of **computational resources**, network bandwith, etc.
But if we had **thousands** of heroes that could consume a lot of **computational resources**, network bandwidth, etc.
So we probably want to limit it.
So, we probably want to limit it.
Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
!!! info
In many cases this is also called **pagination**.
In many cases, this is also called **pagination**.
## Add a Limit and Offset to the Query Parameters
@@ -38,13 +38,13 @@ And by default, we will return a maximum of `100` heroes, so `limit` will have a
</details>
We want to allow clients to set a different `offset` and `limit` values.
We want to allow clients to set different `offset` and `limit` values.
But we don't want them to be able to set a `limit` of something like `9999`, that's over `9000`! 😱
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess **t**han or **e**qual to `100` with `lte=100`.
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess than or **e**qual to `100` with `le=100`.
This way, a client can decide to take less heroes if they want, but not more.
This way, a client can decide to take fewer heroes if they want, but not more.
!!! info
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:

View File

@@ -2,7 +2,7 @@
We have been using the same `Hero` model to declare the schema of the data we receive in the API, the table model in the database, and the schema of the data we send back in responses.
But in most of the cases there are slight differences, let's use multiple models to solve it.
But in most of the cases, there are slight differences. Let's use multiple models to solve it.
Here you will see the main and biggest feature of **SQLModel**. 😎
@@ -10,7 +10,7 @@ Here you will see the main and biggest feature of **SQLModel**. 😎
Let's start by reviewing the automatically generated schemas from the docs UI.
For input we have:
For input, we have:
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/simple-hero-api/image01.png">
@@ -20,7 +20,7 @@ This means that the client could try to use the same ID that already exists in t
That's not what we want.
We want the client to only send the data that is needed to create a new hero:
We want the client only to send the data that is needed to create a new hero:
* `name`
* `secret_name`
@@ -53,17 +53,17 @@ Here's the weird thing, the `id` currently seems also "optional". 🤔
This is because in our **SQLModel** class we declare the `id` with `Optional[int]`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
But in the responses, we would always send a model from the database, and it would **always have an ID**. So the `id` in the responses could be declared as required too.
But in the responses, we always send a model from the database, so it **always has an ID**. So the `id` in the responses can be declared as required.
This would mean that our application is making the compromise with the clients that if it sends a hero, it would for sure have an `id` with a value, it would not be `None`.
This means that our application is making the promise to the clients that if it sends a hero, it will for sure have an `id` with a value, it will not be `None`.
### Why Is it Important to Compromise with the Responses
### Why Is it Important to Have a Contract for Responses
The ultimate goal of an API is for some **clients to use it**.
The clients could be a frontend application, a command line program, a graphical user interface, a mobile application, another backend application, etc.
And the code those clients write depend on what our API tells them they **need to send**, and what they can **expect to receive**.
And the code those clients write depends on what our API tells them they **need to send**, and what they can **expect to receive**.
Making both sides very clear will make it much easier to interact with the API.
@@ -164,7 +164,7 @@ Let's first check how is the process to create a hero now:
Let's check that in detail.
Now we use the type annotation `HeroCreate` for the request JSON data, in the `hero` parameter of the **path operation function**.
Now we use the type annotation `HeroCreate` for the request JSON data in the `hero` parameter of the **path operation function**.
```Python hl_lines="3"
# Code above omitted 👆
@@ -180,9 +180,9 @@ The method `.from_orm()` reads data from another object with attributes and crea
The alternative is `Hero.parse_obj()` that reads data from a dictionary.
But as in this case we have a `HeroCreate` instance in the `hero` variable, this is an object with attributes, so we use `.from_orm()` to read those attributes.
But as in this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.from_orm()` to read those attributes.
With this we create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
With this, we create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
```Python hl_lines="3"
# Code above omitted 👆
@@ -192,7 +192,7 @@ With this we create a new `Hero` instance (the one for the database) and put it
# Code below omitted 👇
```
Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally we return the same `db_hero` variable that has the just refreshed `Hero` instance.
Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally, we return the same `db_hero` variable that has the just refreshed `Hero` instance.
Because it is just refreshed, it has the `id` field set with a new ID taken from the database.
@@ -206,30 +206,30 @@ And now that we return it, FastAPI will validate the data with the `response_mod
# Code below omitted 👇
```
This will validate that all the data that we promised is there, and will remove any data we didn't declare.
This will validate that all the data that we promised is there and will remove any data we didn't declare.
!!! tip
This filtering could be very important, and could be a very good security feature, for example to make sure you filter private data, hashed passwords, etc.
This filtering could be very important and could be a very good security feature, for example, to make sure you filter private data, hashed passwords, etc.
You can read more about it in the <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI docs about Response Model</a>.
In particular, it will make sure that the `id` is there, and that it is indeed an integer (and not `None`).
In particular, it will make sure that the `id` is there and that it is indeed an integer (and not `None`).
## Shared Fields
But looking closely, we could see that these models have a lot of **duplicated information**.
All **the 3 models** declare that thay share some **common fields** that look exactly the same:
All **the 3 models** declare that they share some **common fields** that look exactly the same:
* `name`, required
* `secret_name`, required
* `age`, optional
And then they declare other fields with some differences (in this case only about the `id`).
And then they declare other fields with some differences (in this case, only about the `id`).
We want to **avoid duplicated information** if possible.
This is important if, for example, in the future we decide to **refactor the code** and rename one field (column). For example, from `secret_name` to `secret_identity`.
This is important if, for example, in the future, we decide to **refactor the code** and rename one field (column). For example, from `secret_name` to `secret_identity`.
If we have that duplicated in multiple models, we could easily forget to update one of them. But if we **avoid duplication**, there's only one place that would need updating. ✨
@@ -305,6 +305,31 @@ And of course, all these fields will be in the columns for the resulting `hero`
And those inherited fields will also be in the **autocompletion** and **inline errors** in editors, etc.
### Columns and Inheritance with Multiple Models
Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`.
```Python hl_lines="4 6 9"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py[ln:7-14]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!}
```
</details>
This won't affect this parent **data model** `HeroBase`.
But once the child model `Hero` (the actual **table model**) inherits those fields, it will use those field configurations to create the indexes when creating the tables in the database.
### The `HeroCreate` **Data Model**
Now let's see the `HeroCreate` model that will be used to define the data that we want to receive in the API when creating a new hero.
@@ -336,9 +361,9 @@ And because we can't leave the empty space when creating a new class, but we don
This means that there's nothing else special in this class apart from the fact that it is named `HeroCreate` and that it inherits from `HeroBase`.
As an alternative, we could use `HeroBase` directly in the API code instead of `HeroCreate`, but it would show up in the auomatic docs UI with that name "`HeroBase`" which could be **confusing** for clients. Instead, "`HeroCreate`" is a bit more explicit about what it is for.
As an alternative, we could use `HeroBase` directly in the API code instead of `HeroCreate`, but it would show up in the automatic docs UI with that name "`HeroBase`" which could be **confusing** for clients. Instead, "`HeroCreate`" is a bit more explicit about what it is for.
On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example a password), and now we already have the class to put those extra fields.
On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example, a password), and now we already have the class to put those extra fields.
### The `HeroRead` **Data Model**
@@ -365,7 +390,7 @@ This one just declares that the `id` field is required when reading a hero from
## Review the Updated Docs UI
The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroRead`. But now we define them in a smarter way with inheritance.
The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroRead`. But now, we define them in a smarter way with inheritance.
So, we can jump to the docs UI right away and see how they look with the updated data.
@@ -375,7 +400,7 @@ Let's see the new UI for creating a hero:
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/multiple-models/image02.png">
Nice! It now shows that to create a hero, we just pass the `name`, `secret_name`, and optinally `age`.
Nice! It now shows that to create a hero, we just pass the `name`, `secret_name`, and optionally `age`.
We no longer pass an `id`.
@@ -391,7 +416,7 @@ And if we check the schema for the **Read Heroes** *path operation* it will also
## Inheritance and Table Models
We just saw how powerful inheritance of these models can be.
We just saw how powerful the inheritance of these models could be.
This is a very simple example, and it might look a bit... meh. 😅

View File

@@ -42,7 +42,7 @@ But if the integer is not the ID of any hero in the database, it will not find a
So, we check it in an `if` block, if it's `None`, we raise an `HTTPException` with a `404` status code.
And to use it we first import `HTTPException` from `fastapi`.
And to use it, we first import `HTTPException` from `fastapi`.
This will let the client know that they probably made a mistake on their side and requested a hero that doesn't exist in the database.

View File

@@ -55,11 +55,11 @@ And the same way, we declared the `TeamRead` with only the same base fields of t
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:32-37]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:31-36]!}
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:46-47]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:45-46]!}
# Code below omitted 👇
```
@@ -80,11 +80,11 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s
```Python hl_lines="3 8 12 17"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:105-110]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:104-109]!}
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:160-165]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:158-163]!}
# Code below omitted 👇
```
@@ -102,7 +102,7 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s
Now let's stop for a second and think about it.
We cannot simply include *all* the data including all the internal relationships, because each **hero** has an attribute `team` with their team, and then that **team** also has an attribute `heroes` with all the **heroes** in the team, including this one.
We cannot simply include *all* the data, including all the internal relationships, because each **hero** has an attribute `team` with their team, and then that **team** also has an attribute `heroes` with all the **heroes** in the team, including this one.
If we tried to include everything, we could make the server application **crash** trying to extract **infinite data**, going through the same hero and team over and over again internally, something like this:
@@ -152,7 +152,7 @@ If we tried to include everything, we could make the server application **crash*
}
```
As you can see, in this example we would get the hero **Rusty-Man**, and from this hero we would get the team **Preventers**, and then from this team we would get its heroes, of course, including **Rusty-Man**... 😱
As you can see, in this example, we would get the hero **Rusty-Man**, and from this hero we would get the team **Preventers**, and then from this team we would get its heroes, of course, including **Rusty-Man**... 😱
So we start again, and in the end, the server would just crash trying to get all the data with a `"Maximum recursion error"`, we would not even get a response like the one above.
@@ -164,7 +164,7 @@ This is a decision that will depend on **each application**.
In our case, let's say that if we get a **list of heroes**, we don't want to also include each of their teams in each one.
And if we get a **list of teams**, we don't want to get a a list of the heroes for each one.
And if we get a **list of teams**, we don't want to get a list of the heroes for each one.
But if we get a **single hero**, we want to include the team data (without the team's heroes).
@@ -195,7 +195,7 @@ We'll add them **after** the other models so that we can easily reference the pr
</details>
These two models are very **simple in code**, but there's a lot happening here, let's check it out.
These two models are very **simple in code**, but there's a lot happening here. Let's check it out.
### Inheritance and Type Annotations
@@ -203,7 +203,7 @@ The `HeroReadWithTeam` **inherits** from `HeroRead`, which means that it will ha
And then it adds the **new field** `team`, which could be `None`, and is declared with the type `TeamRead` with the base fields for reading a team.
Then we do the same for the `TeamReadWithHeroes`, it **inherits** from `TeamRead`, and declare the **new field** `heroes` which is a list of `HeroRead`.
Then we do the same for the `TeamReadWithHeroes`, it **inherits** from `TeamRead`, and declares the **new field** `heroes`, which is a list of `HeroRead`.
### Data Models Without Relationship Attributes
@@ -213,7 +213,7 @@ Instead, here these are only **data models** that will tell FastAPI **which attr
### Reference to Other Models
Also notice that the field `team` is not declared with this new `TeamReadWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamRead` model.
Also, notice that the field `team` is not declared with this new `TeamReadWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamRead` model.
And the same for `TeamReadWithHeroes`, the model used for the new field `heroes` uses `HeroRead` to get only each hero's data.
@@ -234,7 +234,7 @@ In the case of the hero, this tells FastAPI to extract the `team` too. And in th
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:168-173]!}
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:167-172]!}
# Code below omitted 👇
```
@@ -326,7 +326,7 @@ Now we get the list of **heroes** included:
## Recap
Using the same techniques to declare additonal **data models** we can tell FastAPI what data to return in the responses, even when we return **table models**.
Using the same techniques to declare additional **data models**, we can tell FastAPI what data to return in the responses, even when we return **table models**.
Here we almost **didn't have to change the FastAPI app** code, but of course, there will be cases where you need to get the data and process it in different ways in the *path operation function* before returning it.
@@ -334,4 +334,4 @@ But even in those cases, you will be able to define the **data models** to use i
By this point, you already have a very robust API to handle data in a SQL database combining **SQLModel** with **FastAPI**, and implementing **best practices**, like data validation, conversion, filtering, and documentation. ✨
In the next chapter I'll tell you how to implement automated **testing** for your application using FastAPI and SQLModel. ✅
In the next chapter, I'll tell you how to implement automated **testing** for your application using FastAPI and SQLModel. ✅

View File

@@ -22,7 +22,7 @@ You can see that there's a possible "Successful Response" with a code `200`, but
<img class="shadow" alt="API docs UI without response data schemas" src="/img/tutorial/fastapi/response-model/image01.png">
Right now we only tell FastAPI the data we want to receive, but we don't tell it yet the data we want to send back.
Right now, we only tell FastAPI the data we want to receive, but we don't tell it yet the data we want to send back.
Let's do that now. 🤓

View File

@@ -90,7 +90,7 @@ We import `Depends()` from `fastapi`. Then we use it in the *path operation func
You can read more about it in the FastAPI documentation <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks" class="external-link" target="_blank">Path Parameters and Numeric Validations - Order the parameters as you need, tricks</a>
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code, and will give you the value from that dependency.
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code and will give you the value from that dependency.
If it had `yield`, then it will continue the rest of the execution once you are done sending the response. In the case of the **session**, it will finish the cleanup code from the `with` block, closing the session, etc.
@@ -177,7 +177,7 @@ And then we remove the previous `with` block with the old **session**.
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-107]!}
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-106]!}
```
<details>

View File

@@ -23,7 +23,7 @@ $ python -m pip install fastapi "uvicorn[standard]"
```
</div>
s
## **SQLModel** Code - Models, Engine
Now let's start with the SQLModel code.
@@ -152,13 +152,13 @@ It will be called when a user sends a request with a `POST` **operation** to the
## The **SQLModel** Advantage
Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same tieme shine. ✨
Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shine. ✨
Here we use the **same** class model to define the **request body** that will be received by our API.
Because **FastAPI** is based on Pydantic, it will use the same model (the Pydantic part) to do automatic data validation and <abbr title="also called serialization, marshalling">conversion</abbr> from the JSON request to an object that is an actual instance of the `Hero` class.
And then because this same **SQLModel** object is not only a **Pydantic** model instance but also a **SQLAlchemy** model instance, we can use it directly in a **session** to create the row in the database.
And then, because this same **SQLModel** object is not only a **Pydantic** model instance but also a **SQLAlchemy** model instance, we can use it directly in a **session** to create the row in the database.
So we can use intuitive standard Python **type annotations**, and we don't have to duplicate a lot of the code for the database models and the API data models. 🎉
@@ -190,13 +190,13 @@ When a client sends a request to the **path** `/heroes/` with a `GET` HTTP **ope
## One Session per Request
Remember that we shoud use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session?
Remember that we should use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session?
Here it is much more obvious.
We should normally have **one session per request** in most of the cases.
In some isolated cases we would want to have new sessions inside, so, **more than one session** per request.
In some isolated cases, we would want to have new sessions inside, so, **more than one session** per request.
But we would **never want to *share* the same session** among different requests.
@@ -277,7 +277,7 @@ And then you can get them back with the **Read Heroes** *path operation*:
Now you can terminate that Uvicorn server by going back to the terminal and pressing <kbd>Ctrl+C</kbd>.
And then you can open **DB Browser for SQLite** and check the database, to explore the data and confirm that it indeed saved the heroes. 🎉
And then, you can open **DB Browser for SQLite** and check the database, to explore the data and confirm that it indeed saved the heroes. 🎉
<img class="shadow" alt="DB Browser for SQLite showing the heroes" src="/img/tutorial/fastapi/simple-hero-api/db-browser-01.png">
@@ -287,4 +287,4 @@ Good job! This is already a FastAPI **web API** application to interact with the
There are several things we can improve and extend. For example, we want the database to decide the ID of each new hero, we don't want to allow a user to send it.
We will do all those improvements in the next chapters. 🚀
We will make all those improvements in the next chapters. 🚀

View File

@@ -1,4 +1,4 @@
# FastAPI Path Opeartions for Teams - Other Models
# FastAPI Path Operations for Teams - Other Models
Let's now update the **FastAPI** application to handle data for teams.
@@ -12,14 +12,14 @@ Let's add the models for the teams.
It's the same process we did for heroes, with a base model, a **table model**, and some other **data models**.
We have a `TeamBase` **data model**, and from it we inherit with a `Team` **table model**.
We have a `TeamBase` **data model**, and from it, we inherit with a `Team` **table model**.
Then we also inherit from the `TeamBase` for the `TeamCreate` and `TeamRead` **data models**.
And we also create a `TeamUpdate` **data model**.
```Python hl_lines="7-9 12-15 18-19 22-23 26-29"
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:1-29]!}
```Python hl_lines="7-9 12-15 18-19 22-23 26-28"
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:1-28]!}
# Code below omitted 👇
```
@@ -42,7 +42,7 @@ Let's now update the `Hero` models too.
```Python hl_lines="3-8 11-15 17-18 21-22 25-29"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:32-58]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:31-57]!}
# Code below omitted 👇
```
@@ -66,10 +66,10 @@ And even though the `HeroBase` is *not* a **table model**, we can declare `team_
Notice that the **relationship attributes**, the ones with `Relationship()`, are **only** in the **table models**, as those are the ones that are handled by **SQLModel** with SQLAlchemy and that can have the automatic fetching of data from the database when we access them.
```Python hl_lines="11 39"
```Python hl_lines="11 38"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:7-58]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:7-57]!}
# Code below omitted 👇
```
@@ -92,7 +92,7 @@ These are equivalent and very similar to the **path operations** for the **heroe
```Python hl_lines="3-9 12-20 23-28 31-47 50-57"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:140-194]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:138-192]!}
# Code below omitted 👇
```
@@ -108,9 +108,9 @@ These are equivalent and very similar to the **path operations** for the **heroe
## Using Relationships Attributes
Up to this point we are actually not using the **relationship attributes**, but we could access them in our code.
Up to this point, we are actually not using the **relationship attributes**, but we could access them in our code.
In the next chapter we will play more with them.
In the next chapter, we will play more with them.
## Check the Docs UI

View File

@@ -76,13 +76,13 @@ Let's start with a simple test, with just the basic test code we need the check
That's the **core** of the code we need for all the tests later.
But now we need to deal with a bit of logistics and details we are not paying attention to just yet. 🤓
But now, we need to deal with a bit of logistics and details we are not paying attention to just yet. 🤓
## Testing Database
This test looks fine, but there's a problem.
If we run it, it will use the same **production database** that we are using to store our very important **heroes**, and we will end up adding adding unnecesary data to it, or even worse, in future tests we could end up removing production data.
If we run it, it will use the same **production database** that we are using to store our very important **heroes**, and we will end up adding unnecessary data to it, or even worse, in future tests we could end up removing production data.
So, we should use an independent **testing database**, just for the tests.
@@ -155,7 +155,7 @@ That way, when we call `.create_all()` all the **table models** are correctly re
## Memory Database
Now we are not using the production database, instead we use a **new testing database** with the `testing.db` file, which is great.
Now we are not using the production database. Instead, we use a **new testing database** with the `testing.db` file, which is great.
But SQLite also supports having an **in memory** database. This means that all the database is only in memory, and it is never saved in a file on disk.
@@ -171,7 +171,7 @@ Other alternatives and ideas 👀
</summary>
Before arriving at the idea of using an **in-memory database** we could have explored other alternatives and ideas.
The first, is that we are not deleting the file after we finish the test, so, the next test could have **leftover data**. So, the right thing would be to delete the file right after finishing the test. 🔥
The first is that we are not deleting the file after we finish the test, so the next test could have **leftover data**. So, the right thing would be to delete the file right after finishing the test. 🔥
But if each test has to create a new file and then delete it afterwards, running all the tests could be **a bit slow**.
@@ -179,7 +179,7 @@ Right now, we have a file `testing.db` that is used by all the tests (we only ha
So, if we tried to run the tests at the same time **in parallel** to try to speed things up a bit, they would clash trying to use the *same* `testing.db` file.
Of couse, we could also fix that, using some **random name** for each testing database file... but in the case of SQLite, we have an even better alternative with just using an **in-memory database**. ✨
Of course, we could also fix that, using some **random name** for each testing database file... but in the case of SQLite, we have an even better alternative by just using an **in-memory database**. ✨
</details>
@@ -208,7 +208,7 @@ And all the other tests can do the same.
Great, that works, and you could replicate all that process in each of the test functions.
But we had to add a lot of **boilerplate code** to handle the custom database, creating it in memory, the custom session, the dependency override.
But we had to add a lot of **boilerplate code** to handle the custom database, creating it in memory, the custom session, and the dependency override.
Do we really have to duplicate all that for **each test**? No, we can do better! 😎
@@ -242,12 +242,12 @@ Let's see the first code example with a fixture:
**pytest** fixtures work in a very similar way to FastAPI dependencies, but have some minor differences:
* In pytest fixtures we need to add a decorator of `@pytest.fixture()` on top.
* In pytest fixtures, we need to add a decorator of `@pytest.fixture()` on top.
* To use a pytest fixture in a function, we have to declare the parameter with the **exact same name**. In FastAPI we have to **explicitly use `Depends()`** with the actual function inside it.
But apart from the way we declare them and how we tell the framework that we want to have them in the function, they **work in a very similar way**.
Now we create lot's of tests, and re-use that same fixture in all of them, saving us that **boilerplate code**.
Now we create lot's of tests and re-use that same fixture in all of them, saving us that **boilerplate code**.
**pytest** will make sure to run them right before (and finish them right after) each test function. So, each test function will actually have its own database, engine, and session.
@@ -255,7 +255,7 @@ Now we create lot's of tests, and re-use that same fixture in all of them, savin
Awesome, that fixture helps us prevent a lot of duplicated code.
But currently we still have to write some code in the test function that will be repetitive for other tests, right now we:
But currently, we still have to write some code in the test function that will be repetitive for other tests, right now we:
* create the **dependency override**
* put it in the `app.dependency_overrides`
@@ -277,7 +277,7 @@ So, we can create a **client fixture** that will be used in all the tests, and i
!!! tip
Check out the number bubbles to see what is done by each line of code.
Now we have a **client fixture** that in turns uses the **session fixture**.
Now we have a **client fixture** that, in turn, uses the **session fixture**.
And in the actual test function, we just have to declare that we require this **client fixture**.
@@ -285,7 +285,7 @@ And in the actual test function, we just have to declare that we require this **
At this point, it all might seem like we just did a lot of changes for nothing, to get **the same result**. 🤔
But normally we will create **lots of other test functions**. And now all the boilerplate and complexity is **writen only once**, in those two fixtures.
But normally we will create **lots of other test functions**. And now all the boilerplate and complexity is **written only once**, in those two fixtures.
Let's add some more tests:
@@ -311,13 +311,13 @@ Let's add some more tests:
That's why we add these two extra tests here.
Now, any additional test functions can be as **simple** as the first one, they just have to **declate the `client` parameter** to get the `TestClient` **fixture** with all the database stuff setup. Nice! 😎
Now, any additional test functions can be as **simple** as the first one, they just have to **declare the `client` parameter** to get the `TestClient` **fixture** with all the database stuff setup. Nice! 😎
## Why Two Fixtures
Now, seeing the code we could think, why do we put **two fixtures** instead of **just one** with all the code? And that makes total sense!
Now, seeing the code, we could think, why do we put **two fixtures** instead of **just one** with all the code? And that makes total sense!
For these examples, **that would have been simpler**, there's no need to separate that code in two fixtures for them...
For these examples, **that would have been simpler**, there's no need to separate that code into two fixtures for them...
But for the next test function, we will require **both fixtures**, the **client** and the **session**.
@@ -340,7 +340,7 @@ But for the next test function, we will require **both fixtures**, the **client*
</details>
In this test function we want to check that the *path operation* to **read a list of heroes** actually sends us heroes.
In this test function, we want to check that the *path operation* to **read a list of heroes** actually sends us heroes.
But if the **database is empty**, we would get an **empty list**, and we wouldn't know if the hero data is being sent correctly or not.
@@ -362,7 +362,7 @@ The function for the **client fixture** and the actual testing function will **b
## Add the Rest of the Tests
Using the same ideas, requiring the fixtures, creating data that we need for the tests, etc. we can now add the rest of the tests, they look quite similar to what we have done up to now.
Using the same ideas, requiring the fixtures, creating data that we need for the tests, etc., we can now add the rest of the tests. They look quite similar to what we have done up to now.
```Python hl_lines="3 18 33"
# Code above omitted 👆
@@ -406,9 +406,9 @@ project/test_main.py <font color="#A6E22E">....... [100%]</font>
Did you read all that? Wow, I'm impressed! 😎
Adding tests to your application will give you a lot of **certainty** that everything is **working correctly**, as you indended.
Adding tests to your application will give you a lot of **certainty** that everything is **working correctly**, as you intended.
And tests will be notoriously useful when **refactoring** your code, **changing things**, **adding features**. Because tests they can help catch a lot of errors that can be easily introduced by refactoring.
And tests will be notoriously useful when **refactoring** your code, **changing things**, **adding features**. Because tests can help catch a lot of errors that can be easily introduced by refactoring.
And they will give you the confidence to work faster and **more efficiently**, because you know that you are checking if you are **not breaking anything**. 😅

View File

@@ -4,7 +4,7 @@ Now let's see how to update data in the database with a **FastAPI** *path operat
## `HeroUpdate` Model
We want clients to be able to udpate the `name`, the `secret_name`, and the `age` of a hero.
We want clients to be able to update the `name`, the `secret_name`, and the `age` of a hero.
But we don't want them to have to include all the data again just to **update a single field**.
@@ -61,7 +61,7 @@ We will use a `PATCH` HTTP operation. This is used to **partially update data**,
</details>
We also read the `hero_id` from the *path parameter* an the request body, a `HeroUpdate`.
We also read the `hero_id` from the *path parameter* and the request body, a `HeroUpdate`.
### Read the Existing Hero
@@ -100,7 +100,7 @@ But that also means that if we just call `hero.dict()` we will get a dictionary
}
```
And then if we update the hero in the database with this data, we would be removing any existing values, and that's probably **not what the client intended**.
And then, if we update the hero in the database with this data, we would be removing any existing values, and that's probably **not what the client intended**.
But fortunately Pydantic models (and so SQLModel models) have a parameter we can pass to the `.dict()` method for that: `exclude_unset=True`.
@@ -200,7 +200,7 @@ We are **not simply omitting** the data that has the **default values**.
And we are **not simply omitting** anything that is `None`.
This means that, if a model in the database **has a value different than the default**, the client could **reset it to the same value as the default**, or even `None`, and we would **still notice it** and **update it accordingly**. 🤯🚀
This means that if a model in the database **has a value different than the default**, the client could **reset it to the same value as the default**, or even `None`, and we would **still notice it** and **update it accordingly**. 🤯🚀
So, if the client wanted to intentionally remove the `age` of a hero, they could just send a JSON with:
@@ -218,11 +218,11 @@ And when getting the data with `hero.dict(exclude_unset=True)`, we would get:
}
```
So, we would use that value and upate the `age` to `None` in the database, **just as the client intended**.
So, we would use that value and update the `age` to `None` in the database, **just as the client intended**.
Notice that `age` here is `None`, and **we still detected it**.
Also that `name` was not even sent, and we don't *accidentaly* set it to `None` or something, we just didn't touch it, because the client didn't sent it, so we are **pefectly fine**, even in these corner cases. ✨
Also, that `name` was not even sent, and we don't *accidentally* set it to `None` or something. We just didn't touch it because the client didn't send it, so we are **perfectly fine**, even in these corner cases. ✨
These are some of the advantages of Pydantic, that we can use with SQLModel. 🎉

View File

@@ -2,11 +2,11 @@
## Type hints
If you need a refreshed about how to use Python type hints (type annotations), check <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">FastAPI's Python types intro</a>.
If you need a refresher about how to use Python type hints (type annotations), check <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">FastAPI's Python types intro</a>.
You can also check the <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">mypy cheat sheet</a>.
**SQLModel** uses type annotations for everything, this way you can use a familiar Python syntax and get all the editor support posible, with autocompletion and in-editor error checking.
**SQLModel** uses type annotations for everything, this way you can use a familiar Python syntax and get all the editor support possible, with autocompletion and in-editor error checking.
## Intro
@@ -64,15 +64,13 @@ $ cd sqlmodel-tutorial
Make sure you have an officially supported version of Python.
Currently it is **Python 3.6** and above (Python 3.5 was already deprecated).
You can check which version you have with:
<div class="termy">
```console
$ python3 --version
Python 3.6.9
Python 3.11
```
</div>
@@ -84,8 +82,6 @@ You might want to try with the specific versions, for example with:
* `python3.10`
* `python3.9`
* `python3.8`
* `python3.7`
* `python3.6`
The code would look like this:
@@ -97,7 +93,7 @@ $ python3 --version
// This is too old! 😱
Python 3.5.6
// Let's see if python3.10 is available
$ python3.10 --verson
$ python3.10 --version
// Oh, no, this one is not available 😔
command not found: python3.10
$ python3.9 --version
@@ -136,10 +132,10 @@ Here are the commands you could use:
<div class="termy">
```console
// Remember that you might need to use python3.9 or similar 💡
// Remember that you might need to use python3.9 or similar 💡
// Create the virtual environment using the module "venv"
$ python3 -m venv env
// ...here it creates the virtual enviroment in the directory "env"
// ...here it creates the virtual environment in the directory "env"
// Activate the virtual environment
$ source ./env/bin/activate
// Verify that the virtual environment is active
@@ -161,7 +157,7 @@ Here are the commands you could use:
```console
// Create the virtual environment using the module "venv"
# >$ python3 -m venv env
// ...here it creates the virtual enviroment in the directory "env"
// ...here it creates the virtual environment in the directory "env"
// Activate the virtual environment
# >$ .\env\Scripts\Activate.ps1
// Verify that the virtual environment is active

406
docs/tutorial/indexes.md Normal file
View File

@@ -0,0 +1,406 @@
# Indexes - Optimize Queries
We just saw how to get some data `WHERE` a **condition** is true. For example, where the hero **name is "Deadpond"**.
If we just create the tables and the data as we have been doing, when we `SELECT` some data using `WHERE`, the database would have to **scan** through **each one of the records** to find the ones that **match**. This is not a problem with 3 heroes as in these examples.
But imagine that your database has **thousands** or **millions** of **records**, if every time you want to find the heroes with the name "Deadpond" it has to scan through **all** of the records to find all the possible matches, then that becomes problematic, as it would be too slow.
I'll show you how to handle it with a database **index**.
The change in the code is **extremely small**, but it's useful to understand what's happening behind the scenes, so I'll show you **how it all works** and what it means.
---
If you already executed the previous examples and have a database with data, **remove the database file** before running each example, that way you won't have duplicate data and you will be able to get the same results.
## No Time to Explain
Are you already a **SQL expert** and don't have time for all my explanations?
Fine, in that case, you can **sneak peek** the final code to create indexes here.
<details>
<summary>👀 Full file preview</summary>
```Python hl_lines="8 10"
{!./docs_src/tutorial/indexes/tutorial002.py!}
```
</details>
..but if you are not an expert, **continue reading**, this will probably be useful. 🤓
## What is an Index
In general, an **index** is just something we can have to help us **find things faster**. It normally works by having things in **order**. Let's think about some real-life examples before even thinking about databases and code.
### An Index and a Dictionary
Imagine a **dictionary**, a book with definitions of words. 📔 ...not a Python `dict`. 😅
Let's say that you want to **find a word**, for example the word "**database**". You take the dictionary, and open it somewhere, for example in the middle. Maybe you see some definitions of words that start with `m`, like `manual`, so you conclude that you are in the letter `m` in the dictionary.
<img src="/img/tutorial/indexes/dictionary001.svg">
You know that in the alphabet, the letter `d` for `database` comes **before** the letter `m` for `manual`.
<img src="/img/tutorial/indexes/dictionary002.svg">
So, you know you have to search in the dictionary **before** the point you currently are. You still don't know where the word `database` is, because you don't know exactly where the letter `d` is in the dictionary, but you know that **it is not after** that point, you can now **discard the right half** of the dictionary in your search.
<img src="/img/tutorial/indexes/dictionary003.svg">
Next, you **open the dictionary again**, but only taking into account the **half of the dictionary** that can contain the word you want, the **left part of the dictionary**. You open it in the middle of that left part and now you arrive maybe at the letter `f`.
<img src="/img/tutorial/indexes/dictionary004.svg">
You know that `d` from `database` comes before `f`. So it has to be **before** that. But now you know that `database` **is not after** that point, and you can discard the dictionary from that point onward.
<img src="/img/tutorial/indexes/dictionary005.svg">
Now you have a **small section of dictionary** to search (only a **quarter** of dictionary can have your word). You take that **quarter** of the pages at the start of the dictionary that can contain your word, and open it in the middle of that section. Maybe you arrive at the letter `c`.
<img src="/img/tutorial/indexes/dictionary006.svg">
You know the word `database` has to be **after** that and **not before** that point, so you can discard the left part of that block of pages.
<img src="/img/tutorial/indexes/dictionary007.svg">
You repeat this process **a few more times**, and you finally arrive at the letter `d`, you continue with the same process in that section for the letter `d` and you finally **find the word** `database`. 🎉
<img src="/img/tutorial/indexes/dictionary008.svg">
You had to open the dictionary a few times, maybe **5 or 10**. That's actually **very little work** compared to what it could have been.
!!! note "Technical Details"
Do you like **fancy words**? Cool! Programmers tend to like fancy words. 😅
That <abbr title="a recipe, a sequence of predefined steps that achieve a result">algorithm</abbr> I showed you above is called **Binary Search**.
It's called like that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want.
### An Index and a Novel
Let's now imagine you are reading a **novel book**. And someone told you that at some point, they mention a **database**, and you want to find that chapter.
How do you find the word "*database*" there? You might have to read **the entire book** to find where the word "*database*" is located in the book. So, instead of opening the book 5 or 10 times, you would have to open each of the **500 pages** and read them one by one until you find the word. You might enjoy the book, though. 😅
But if we are only interested in **quickly finding information** (as when working with SQL databases), then reading each of the 500 pages is **too inefficient** when there could be an option to open the book in 5 or 10 places and find what you're looking for.
### A Technical Book with an Index
Now let's imagine you are reading a technical book. For example, with several topics about programming. And there's a couple of sections where it talks about a **database**.
This book might have a **book index**: a section in the book that has some **names of topics covered** and the **page numbers** in the book where you can read about them. And the topic names are **sorted** in alphabetic order, pretty much like a dictionary (a book with words, as in the previous example).
In this case, you can open that book in the end (or in the beginning) to find the **book index** section, it would have only a few pages. And then, you can do the same process as with the **dictionary** example above.
Open the index, and after **5 or 10 steps**, quickly find the topic "**database**" with the page numbers where that is covered, for example "page 253 in Chapter 5". Now you used the dictionary technique to find the **topic**, and that topic gave you a **page number**.
Now you know that you need to find "**page 253**". But by looking at the closed book you still don't know where that page is, so you have to **find that page**. To find it, you can do the same process again, but this time, instead of searching for a **topic** in the **index**, you are searching for a **page number** in the **entire book**. And after **5 or 10 more steps**, you find the page 253 in Chapter 5.
<img src="/img/tutorial/indexes/techbook001.svg">
After this, even though this book is not a dictionary and has some particular content, you were able to **find the section** in the book that talks about a "**database**" in a **few steps** (say 10 or 20, instead of reading all the 500 pages).
The main point is that the index is **sorted**, so we can use the same process we used for the **dictionary** to find the topic. And then that gives us a page number, and the **page numbers are also sorted**! 😅
When we have a list of sorted things we can apply the same technique, and that's the whole trick here, we use the same technique first for the **topics** in the index and then for the **page numbers** to find the actual chapter.
Such efficiency! 😎
## What are Database Indexes
**Database indexes** are very similar to **book indexes**.
Database indexes store some info, some keys, in a way that makes it **easy and fast to find** (for example sorted), and then for each key they **point to some data somewhere else** in the database.
Let's see a more clear example. Let's say you have this table in a database:
<table>
<tr>
<th>id</th><th>name</th><th>secret_name</th><th>age</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td>
</tr>
<tr>
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td>
</tr>
<tr>
<td>3</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td>
</tr>
</table>
And let's imagine you have **many more rows**, many more heroes. Probably **thousands**.
If you tell the SQL database to get you a hero by a specific name, for example `Spider-Boy` (by using the `name` in the `WHERE` part of the SQL query), the database will have to **scan** all the heroes, checking **one by one** to find all the ones with a name of `Spider-Boy`.
In this case, there's only one, but there's nothing limiting the database from having **more records with the same name**. And because of that, the database would **continue searching** and checking each one of the records, which would be very slow.
But now let's say that the database has an index for the column `name`. The index could look something like this, we could imagine that the index is like an additional special table that the database manages automatically:
<table>
<tr>
<th>name</th><th>id</th>
</tr>
<tr>
<td>Deadpond</td><td>1</td>
</tr>
<tr>
<td>Rusty-Man</td><td>3</td>
</tr>
<tr>
<td>Spider-Boy</td><td>2</td>
</tr>
</table>
It would have each `name` field from the `hero` table **in order**. It would not be sorted by `id`, but by `name` (in alphabetical order, as the `name` is a string). So, first it would have `Deadpond`, then `Rusty-Man`, and last `Spider-Boy`. It would also include the `id` of each hero. Remember that this could have **thousands** of heroes.
Then the database would be able to use more or less the same ideas in the examples above with the **dictionary** and the **book index**.
It could start somewhere (for example, in the middle of the index). It could arrive at some hero there in the middle, like `Rusty-Man`. And because the **index** has the `name` fields in order, the database would know that it can **discard all the previous index rows** and **only search** in the following index rows.
<table>
<tr>
<th>name</th><th>id</th>
</tr>
<tr style="background-color: #F5F5F5; color: #999999;">
<td>Deadpond</td><td>1</td>
</tr>
<tr style="background-color: #F5F5F5; color: #999999;">
<td>Rusty-Man</td><td>3</td>
</tr>
<tr style="background-color: #FFF2CC;">
<td>Spider-Boy</td><td>2</td>
</tr>
</table>
And that way, as with the example with the dictionary above, **instead of reading thousands of heroes**, the database would be able to do a few steps, say **5 or 10 steps**, and arrive at the row of the index that has `Spider-Boy`, even if the table (and index) has thousands of rows:
<table>
<tr>
<th>name</th><th>id</th>
</tr>
<tr style="background-color: #F5F5F5; color: #999999;">
<td>Deadpond</td><td>1</td>
</tr>
<tr style="background-color: #F5F5F5; color: #999999;">
<td>Rusty-Man</td><td>3</td>
</tr>
<tr style="background-color: #D5E8D4;">
<td>✨ Spider-Boy ✨</td><td>2</td>
</tr>
</table>
Then by looking at **this index row**, it would know that the `id` for `Spider-Boy` in the `hero` table is `2`.
So then it could **search that `id`** in the `hero` table using more or less the **same technique**.
That way, in the end, instead of reading thousands of records, the database only had to do **a few steps** to find the hero we wanted.
## Updating the Index
As you can imagine, for all this to work, the index would need to be **up to date** with the data in the database.
If you had to update it **manually** in code, it would be very cumbersome and **error-prone**, as it would be easy to end up in a state where the index is not up to date and points to incorrect data. 😱
Here's the good news: when you create an index in a **SQL Database**, the database takes care of **updating** it **automatically** whenever it's necessary. 😎🎉
If you **add new records** to the `hero` table, the database will **automatically** update the index. It will do the **same process** of **finding** the right place to put the new index data (those **5 or 10 steps** described above), and then it will save the new index information there. The same would happen when you **update** or **delete** data.
Defining and creating an index is very **easy** with SQL databases. And then **using it** is even easier... it's transparent. The database will figure out which index to use automatically, the SQL queries don't even change.
So, in SQL databases **indexes are great**! And are super **easy to use**. Why not just have indexes for everything? .....Because indexes also have a "**cost**" in computation and storage (disk space).
## Index Cost
There's a **cost** associated with **indexes**. 💰
When you don't have an index and add a **new row** to the table `hero`, the database has to perform **1 operation** to add the new hero row at the end of the table.
But if you have an **index** for the **hero names**, now the database has to perform the same **1 operation** to add that row **plus** some extra **5 or 10 operations** in the index, to find the right spot for the name, to then add that **index record** there.
And if you have an index for the `name`, one for the `age`, and one for the `secret_name`, now the database has to perform the same **1 operation** to add that row **plus** some extra **5 or 10 operations** in the index **times 3**, for each of the indexes. This means that now adding one row takes something like **31 operations**.
This also means that you are **exchanging** the time it takes to **read** data for the time it takes to **write** data plus some extra **space** in the database.
If you have queries that get data out of the database comparing each one of those fields (for example using `WHERE`), then it makes total sense to have indexes for each one of them. Because **31 operations** while creating or updating data (plus the space of the index) is much, much better than the possible **500 or 1000 operations** to read all the rows to be able to compare them using each field.
But if you **never** have queries that find records by the `secret_name` (you never use `secret_name` in the `WHERE` part) it probably doesn't make sense to have an index for the `secret_name` field/column, as that will increase the computational and space **cost** of writing and updating the database.
## Create an Index with SQL
Phew, that was a lot of theory and explanations. 😅
The most important thing about indexes is **understanding** them, how, and when to use them.
Let's now see the **SQL** syntax to create an **index**. It is very simple:
```SQL hl_lines="3"
CREATE INDEX ix_hero_name
ON hero (name)
```
This means, more or less:
> Hey SQL database 👋, please `CREATE` an `INDEX` for me.
>
> I want the name of the index to be `ix_hero_name`.
>
> This index should be `ON` the table `hero`, it refers to that table.
>
> The column I want you to use for it is `name`.
## Declare Indexes with SQLModel
And now let's see how to define indexes in **SQLModel**.
The change in code is underwhelming, it's very simple. 😆
Here's the `Hero` model we had before:
```Python hl_lines="8"
{!./docs_src/tutorial/where/tutorial001.py[ln:1-10]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/where/tutorial001.py!}
```
</details>
Let's now update it to tell **SQLModel** to create an index for the `name` field when creating the table:
```Python hl_lines="8"
{!./docs_src/tutorial/indexes/tutorial001.py[ln:1-10]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/indexes/tutorial001.py!}
```
</details>
We use the same `Field()` again as we did before, and set `index=True`. That's it! 🚀
Notice that we didn't set an argument of `default=None` or anything similar. This means that **SQLModel** (thanks to Pydantic) will keep it as a **required** field.
!!! info
SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you.
In this case the generated name would be `ix_hero_name`.
## Query Data
Now, to query the data using the field `name` and the new index we don't have to do anything special or different in the code, it's just **the same code**.
The SQL database will figure it out **automatically**. ✨
This is great because it means that indexes are very **simple to use**. But it might also feel counterintuitive at first, as you are **not doing anything** explicitly in the code to make it obvious that the index is useful, it all happens in the database behind the scenes.
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/indexes/tutorial001.py[ln:36-41]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/indexes/tutorial001.py!}
```
</details>
This is exactly the same code as we had before, but now the database will **use the index** underneath.
## Run the Program
If you run the program now, you will see an output like this:
<div class="termy">
```console
$ python app.py
// Some boilerplate output omitted 😉
// Create the table
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
// Create the index 🤓🎉
CREATE INDEX ix_hero_name ON hero (name)
// The SELECT with WHERE looks the same
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00014s] ('Deadpond',)
// The resulting hero
secret_name='Dive Wilson' age=None id=1 name='Deadpond'
```
</div>
## More Indexes
We are going to query the `hero` table doing comparisons on the `age` field too, so we should **define an index** for that one as well:
```Python hl_lines="10"
{!./docs_src/tutorial/indexes/tutorial002.py[ln:1-10]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/indexes/tutorial002.py!}
```
</details>
In this case, we want the default value of `age` to continue being `None`, so we set `default=None` when using `Field()`.
Now when we use **SQLModel** to create the database and tables, it will also create the **indexes** for these two columns in the `hero` table.
So, when we query the database for the `hero` table and use those **two columns** to define what data we get, the database will be able to **use those indexes** to improve the **reading performance**. 🚀
## Primary Key and Indexes
You probably noticed that we didn't set `index=True` for the `id` field.
Because the `id` is already the **primary key**, the database will automatically create an internal **index** for it.
The database always creates an internal index for **primary keys** automatically, as those are the primary way to organize, store, and retrieve data. 🤓
But if you want to be **frequently querying** the SQL database for any **other field** (e.g. using any other field in the `WHERE` section), you will probably want to have at least an **index** for that.
## Recap
**Indexes** are very important to improve **reading performance** and speed when querying the database. 🏎
Creating and using them is very **simple** and easy. The most important part is to understand **how** they work, **when** to create them, and for **which columns**.

View File

@@ -171,7 +171,7 @@ The first step is to import the `Session` class:
```Python hl_lines="3"
{!./docs_src/tutorial/insert/tutorial001.py[ln:1-3]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>

View File

@@ -81,7 +81,7 @@ In this case, instead of getting all the 7 rows, we are limiting them to only ge
<img class="shadow" alt="table with first 3 rows selected" src="/img/tutorial/offset-and-limit/limit.svg">
## Run the Program on the Comamnd Line
## Run the Program on the Command Line
If we run it on the command line, it will output:
@@ -93,7 +93,7 @@ $ python app.py
// Previous output omitted 🙈
// Select with LIMIT
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
LIMIT ? OFFSET ?
INFO Engine [no key 0.00014s] (3, 0)
@@ -153,7 +153,7 @@ Each of those methods applies the change in the internal special select statemen
**Offset** means "skip this many rows", and as we want to skip the ones we already saw, the first three, we use `.offset(3)`.
## Run the Program with Offset on the Comamnd Line
## Run the Program with Offset on the Command Line
Now we can run the program on the command line, and it will output:
@@ -165,7 +165,7 @@ $python app.py
// Previous output omitted 🙈
// Select with LIMIT and OFFSET
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
LIMIT ? OFFSET ?
INFO Engine [no key 0.00020s] (3, 3)
@@ -207,9 +207,9 @@ The database right now has **only 7 rows**, so this query can only get 1 row.
But don't worry, the database won't throw an error trying to get 3 rows when there's only one (as would happen with a Python list).
The database knows that we want to **limit** the number of results, but it doesn't necessarily has to find those many results.
The database knows that we want to **limit** the number of results, but it doesn't necessarily have to find that many results.
## Run the Program with the Last Batch on the Comamnd Line
## Run the Program with the Last Batch on the Command Line
And if we run it in the command line, it will output:
@@ -221,7 +221,7 @@ $ python app.py
// Previous output omitted 🙈
// Select last batch with LIMIT and OFFSET
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
LIMIT ? OFFSET ?
INFO Engine [no key 0.00038s] (3, 6)
@@ -241,7 +241,7 @@ You probably noticed the new SQL keywords `LIMIT` and `OFFSET`.
You can use them in SQL, at the end of the other parts:
```SQL
SELECT id, name, secret_name, age
SELECT id, name, secret_name, age
FROM hero
LIMIT 3 OFFSET 6
```
@@ -271,11 +271,11 @@ Of course, you can also combine `.limit()` and `.offset()` with `.where()` and o
</details>
## Run the Program with Limit and Where on the Comamnd Line
## Run the Program with Limit, Offset, and Where on the Command Line
If we run it on the command line, it will find all the heroes in the database with an age above 32. That would normally be 4 heroes.
But we are limiting the results to only get the first 3:
But we are starting to include after an offset of 1 (so we don't count the first one), and we are limiting the results to only get the first 2 after that:
<div class="termy">
@@ -284,18 +284,17 @@ $ python app.py
// Previous output omitted 🙈
// Select with WHERE and LIMIT
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
// Select with WHERE and LIMIT and OFFSET
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age > ?
LIMIT ? OFFSET ?
INFO Engine [no key 0.00022s] (32, 3, 0)
INFO Engine [no key 0.00022s] (32, 2, 1)
// Print the heroes received, only 3
// Print the heroes received, only 2
[
Hero(age=35, secret_name='Trevor Challa', id=5, name='Black Lion'),
Hero(age=36, secret_name='Steve Weird', id=6, name='Dr. Weird'),
Hero(age=48, secret_name='Tommy Sharp', id=3, name='Rusty-Man')
Hero(age=36, id=6, name='Dr. Weird', secret_name='Steve Weird'),
Hero(age=48, id=3, name='Rusty-Man', secret_name='Tommy Sharp')
]
```

View File

@@ -122,16 +122,16 @@ INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00019s] (1,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001959s ago] (2,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.003215s ago] (3,)
@@ -139,8 +139,8 @@ INFO Engine [cached since 0.003215s ago] (3,)
Deadpond: name='Deadpond' age=None id=1 secret_name='Dive Wilson'
// Accessing the .team attribute triggers a refresh
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [generated in 0.00025s] (1,)
@@ -151,8 +151,8 @@ Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margarets Ba
Rusty-Man: name='Rusty-Man' age=48 id=2 secret_name='Tommy Sharp'
// Accessing the .team attribute triggers a refresh
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.001716s ago] (2,)
@@ -163,8 +163,8 @@ Rusty-Man Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')]
Spider-Boy: name='Spider-Boy' age=None id=3 secret_name='Pedro Parqueador'
// Accessing the .team attribute triggers a refresh
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.002739s ago] (3,)
@@ -179,4 +179,4 @@ INFO Engine ROLLBACK
## Recap
After setting up the model link, using it with **relationship attributes** is fairly straighforward, just Python objects. ✨
After setting up the model link, using it with **relationship attributes** is fairly straightforward, just Python objects. ✨

View File

@@ -40,7 +40,7 @@ And **both fields are primary keys**. We hadn't used this before. 🤓
Let's see the `Team` model, it's almost identical as before, but with a little change:
```Python hl_lines="8"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:15-20]!}
@@ -56,7 +56,7 @@ Let's see the `Team` model, it's almost identical as before, but with a little c
</details>
The **relationship attribute `heroes`** is still a list of heroes, annotatted as `List["Hero"]`. Again, we use `"Hero"` in quotes because we haven't declared that class yet by this point in the code (but as you know, editors and **SQLModel** understand that).
The **relationship attribute `heroes`** is still a list of heroes, annotated as `List["Hero"]`. Again, we use `"Hero"` in quotes because we haven't declared that class yet by this point in the code (but as you know, editors and **SQLModel** understand that).
We use the same **`Relationship()`** function.
@@ -69,7 +69,7 @@ And here's the important part to allow the **many-to-many** relationship, we use
Let's see the other side, here's the `Hero` model:
```Python hl_lines="9"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:23-29]!}
@@ -102,7 +102,7 @@ And now we have a **`link_model=HeroTeamLink`**. ✨
The same as before, we will have the rest of the code to create the **engine**, and a function to create all the tables `create_db_and_tables()`.
```Python hl_lines="9"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:32-39]!}
@@ -122,7 +122,7 @@ The same as before, we will have the rest of the code to create the **engine**,
And as in previous examples, we will add that function to a function `main()`, and we will call that `main()` function in the main block:
```Python hl_lines="4"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:78-79]!}
# We will do more stuff here later 👈
@@ -149,37 +149,37 @@ If you run the code in the command line, it would output:
```console
$ python app.py
// Boilerplate ommited 😉
// Boilerplate omitted 😉
INFO Engine
INFO Engine
CREATE TABLE team (
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00033s] ()
INFO Engine
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00016s] ()
INFO Engine
INFO Engine
// Our shinny new link table ✨
CREATE TABLE heroteamlink (
team_id INTEGER,
hero_id INTEGER,
PRIMARY KEY (team_id, hero_id),
FOREIGN KEY(team_id) REFERENCES team (id),
team_id INTEGER,
hero_id INTEGER,
PRIMARY KEY (team_id, hero_id),
FOREIGN KEY(team_id) REFERENCES team (id),
FOREIGN KEY(hero_id) REFERENCES hero (id)
)

View File

@@ -60,7 +60,7 @@ Notice that each hero can only have **one** connection. But each team can receiv
## Introduce Many-to-Many
But let's say that as **Deadpond** is a great chracter, they recruit him to the new **Preventers** team, but he's still part of the **Z-Force** team too.
But let's say that as **Deadpond** is a great character, they recruit him to the new **Preventers** team, but he's still part of the **Z-Force** team too.
So, now, we need to be able to have a hero that is connected to **many** teams. And then, each team, should still be able to receive **many** heroes. So we need a **Many-to-Many** relationship.

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