├── .env.example ├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── api ├── app.py ├── auth │ └── __init__.py ├── config.py ├── database.py ├── public │ ├── __init__.py │ ├── health │ │ ├── crud.py │ │ ├── models.py │ │ └── views.py │ ├── hero │ │ ├── crud.py │ │ ├── models.py │ │ └── views.py │ └── team │ │ ├── crud.py │ │ ├── models.py │ │ └── views.py └── utils │ ├── generic_models.py │ ├── logger.py │ └── mock_data_generator.py ├── asgi.py ├── entrypoint.sh ├── img ├── SQLModel.png └── SQLModelAPI_openapi.png └── tavern_tests ├── common.yaml ├── test_hero.tavern.yaml └── test_team.tavern.yaml /.env.example: -------------------------------------------------------------------------------- 1 | # to be renamed as .env 2 | ENV=development # allowed development|staging|production 3 | DEBUG=True 4 | LOCAL_DEV=True 5 | API_USER=svc_api_user 6 | API_PASSW=ASuperStrongPassword! 7 | DATABASE_URI=sqlite:////path/to/my/code/fastapi-sqlmodel/database.db -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up Python 3.11 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: "3.11" 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install black pytest 30 | if [ -f Pipfile ]; then pip install pipenv; pipenv install; fi 31 | - name: Lint with black 32 | run: | 33 | black ./ 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .pylintrc 6 | .vscode 7 | database.db 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.7 2 | 3 | 4 | 5 | WORKDIR /app 6 | 7 | COPY tavern_tests /app/tavern_tests 8 | COPY Pipfile /app 9 | COPY Pipfile.lock /app 10 | COPY .env /app/.env 11 | RUN pip install --upgrade pip 12 | RUN pip install pipenv 13 | RUN pipenv install --system --deploy --ignore-pipfile --${PIPENV_ARGS} 14 | 15 | RUN cat /etc/ssl/certs/ca-certificates.crt >> `python -m certifi` 16 | 17 | COPY api/ /app/api 18 | COPY entry_point.py /app/entry_point.py 19 | 20 | EXPOSE 8080 21 | ENTRYPOINT ["/entrypoint.sh"] 22 | CMD ["uvicorn", "asgi:api", "--host", "0.0.0.0", "--port", "8080"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Anthony Cepeda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | fastapi = "*" 8 | sqlmodel = "*" 9 | uvicorn = "*" 10 | pydantic-settings = "*" 11 | 12 | [dev-packages] 13 | black = "*" 14 | pylint = "*" 15 | 16 | [requires] 17 | python_version = "3.11.7" 18 | 19 | [pipenv] 20 | allow_prereleases = true 21 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "b503a28dc37b6b03dc3562e3c593d8f6bf7136ae59b47765f7fba2a5f6548bee" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.11.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "annotated-types": { 20 | "hashes": [ 21 | "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", 22 | "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" 23 | ], 24 | "markers": "python_version >= '3.8'", 25 | "version": "==0.6.0" 26 | }, 27 | "anyio": { 28 | "hashes": [ 29 | "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780", 30 | "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5" 31 | ], 32 | "markers": "python_version >= '3.7'", 33 | "version": "==3.7.1" 34 | }, 35 | "click": { 36 | "hashes": [ 37 | "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", 38 | "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" 39 | ], 40 | "markers": "python_version >= '3.7'", 41 | "version": "==8.1.7" 42 | }, 43 | "fastapi": { 44 | "hashes": [ 45 | "sha256:4d12838819aa52af244580675825e750ad67c9df4614f557a769606af902cf22", 46 | "sha256:f19ebf6fdc82a3281d10f2cb4774bdfa90238e3b40af3525a0c09fd08ad1c480" 47 | ], 48 | "index": "pypi", 49 | "markers": "python_version >= '3.8'", 50 | "version": "==0.105.0" 51 | }, 52 | "greenlet": { 53 | "hashes": [ 54 | "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", 55 | "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6", 56 | "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257", 57 | "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4", 58 | "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", 59 | "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61", 60 | "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", 61 | "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca", 62 | "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7", 63 | "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", 64 | "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", 65 | "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", 66 | "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", 67 | "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414", 68 | "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04", 69 | "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a", 70 | "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf", 71 | "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", 72 | "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", 73 | "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e", 74 | "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274", 75 | "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb", 76 | "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b", 77 | "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9", 78 | "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b", 79 | "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", 80 | "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506", 81 | "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405", 82 | "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113", 83 | "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f", 84 | "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5", 85 | "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", 86 | "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", 87 | "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f", 88 | "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a", 89 | "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", 90 | "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", 91 | "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6", 92 | "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d", 93 | "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71", 94 | "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", 95 | "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", 96 | "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", 97 | "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067", 98 | "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc", 99 | "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881", 100 | "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3", 101 | "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", 102 | "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac", 103 | "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53", 104 | "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0", 105 | "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b", 106 | "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83", 107 | "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41", 108 | "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c", 109 | "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", 110 | "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", 111 | "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" 112 | ], 113 | "markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", 114 | "version": "==3.0.3" 115 | }, 116 | "h11": { 117 | "hashes": [ 118 | "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", 119 | "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" 120 | ], 121 | "markers": "python_version >= '3.7'", 122 | "version": "==0.14.0" 123 | }, 124 | "idna": { 125 | "hashes": [ 126 | "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", 127 | "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" 128 | ], 129 | "markers": "python_version >= '3.5'", 130 | "version": "==3.6" 131 | }, 132 | "pydantic": { 133 | "hashes": [ 134 | "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a", 135 | "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4" 136 | ], 137 | "markers": "python_version >= '3.7'", 138 | "version": "==2.5.3" 139 | }, 140 | "pydantic-core": { 141 | "hashes": [ 142 | "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556", 143 | "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e", 144 | "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411", 145 | "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245", 146 | "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c", 147 | "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66", 148 | "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd", 149 | "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d", 150 | "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b", 151 | "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06", 152 | "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948", 153 | "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341", 154 | "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0", 155 | "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f", 156 | "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a", 157 | "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2", 158 | "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51", 159 | "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80", 160 | "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8", 161 | "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d", 162 | "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8", 163 | "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb", 164 | "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590", 165 | "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87", 166 | "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534", 167 | "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b", 168 | "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145", 169 | "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba", 170 | "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b", 171 | "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2", 172 | "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e", 173 | "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052", 174 | "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622", 175 | "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab", 176 | "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b", 177 | "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66", 178 | "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e", 179 | "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4", 180 | "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e", 181 | "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec", 182 | "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c", 183 | "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed", 184 | "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937", 185 | "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f", 186 | "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9", 187 | "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4", 188 | "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96", 189 | "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277", 190 | "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23", 191 | "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7", 192 | "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b", 193 | "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91", 194 | "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d", 195 | "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e", 196 | "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1", 197 | "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2", 198 | "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160", 199 | "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9", 200 | "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670", 201 | "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7", 202 | "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c", 203 | "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb", 204 | "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42", 205 | "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d", 206 | "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8", 207 | "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1", 208 | "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6", 209 | "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8", 210 | "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf", 211 | "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e", 212 | "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a", 213 | "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9", 214 | "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1", 215 | "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40", 216 | "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2", 217 | "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d", 218 | "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f", 219 | "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f", 220 | "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af", 221 | "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7", 222 | "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda", 223 | "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a", 224 | "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95", 225 | "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0", 226 | "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60", 227 | "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149", 228 | "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975", 229 | "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4", 230 | "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe", 231 | "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94", 232 | "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03", 233 | "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c", 234 | "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b", 235 | "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a", 236 | "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24", 237 | "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391", 238 | "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c", 239 | "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab", 240 | "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd", 241 | "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786", 242 | "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08", 243 | "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8", 244 | "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6", 245 | "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0", 246 | "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421" 247 | ], 248 | "markers": "python_version >= '3.7'", 249 | "version": "==2.14.6" 250 | }, 251 | "pydantic-settings": { 252 | "hashes": [ 253 | "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c", 254 | "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a" 255 | ], 256 | "index": "pypi", 257 | "markers": "python_version >= '3.8'", 258 | "version": "==2.1.0" 259 | }, 260 | "python-dotenv": { 261 | "hashes": [ 262 | "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", 263 | "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" 264 | ], 265 | "markers": "python_version >= '3.8'", 266 | "version": "==1.0.0" 267 | }, 268 | "sniffio": { 269 | "hashes": [ 270 | "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", 271 | "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" 272 | ], 273 | "markers": "python_version >= '3.7'", 274 | "version": "==1.3.0" 275 | }, 276 | "sqlalchemy": { 277 | "hashes": [ 278 | "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3", 279 | "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884", 280 | "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74", 281 | "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d", 282 | "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc", 283 | "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca", 284 | "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d", 285 | "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf", 286 | "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846", 287 | "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306", 288 | "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221", 289 | "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5", 290 | "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89", 291 | "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55", 292 | "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72", 293 | "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea", 294 | "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8", 295 | "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577", 296 | "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df", 297 | "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4", 298 | "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d", 299 | "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34", 300 | "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4", 301 | "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24", 302 | "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6", 303 | "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965", 304 | "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35", 305 | "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b", 306 | "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab", 307 | "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22", 308 | "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4", 309 | "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204", 310 | "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855", 311 | "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d", 312 | "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab", 313 | "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69", 314 | "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693", 315 | "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e", 316 | "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8", 317 | "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0", 318 | "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45", 319 | "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab", 320 | "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1", 321 | "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d", 322 | "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda", 323 | "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b", 324 | "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18", 325 | "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac", 326 | "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60" 327 | ], 328 | "markers": "python_version >= '3.7'", 329 | "version": "==2.0.23" 330 | }, 331 | "sqlmodel": { 332 | "hashes": [ 333 | "sha256:0bff8fc94af86b44925aa813f56cf6aabdd7f156b73259f2f60692c6a64ac90e", 334 | "sha256:accea3ff5d878e41ac439b11e78613ed61ce300cfcb860e87a2d73d4884cbee4" 335 | ], 336 | "index": "pypi", 337 | "markers": "python_version >= '3.7' and python_version < '4.0'", 338 | "version": "==0.0.14" 339 | }, 340 | "starlette": { 341 | "hashes": [ 342 | "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75", 343 | "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91" 344 | ], 345 | "markers": "python_version >= '3.7'", 346 | "version": "==0.27.0" 347 | }, 348 | "typing-extensions": { 349 | "hashes": [ 350 | "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", 351 | "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" 352 | ], 353 | "markers": "python_version >= '3.8'", 354 | "version": "==4.9.0" 355 | }, 356 | "uvicorn": { 357 | "hashes": [ 358 | "sha256:6dddbad1d7ee0f5140aba5ec138ddc9612c5109399903828b4874c9937f009c2", 359 | "sha256:ce107f5d9bd02b4636001a77a4e74aab5e1e2b146868ebbad565237145af444c" 360 | ], 361 | "index": "pypi", 362 | "markers": "python_version >= '3.8'", 363 | "version": "==0.25.0" 364 | } 365 | }, 366 | "develop": { 367 | "aiohttp": { 368 | "hashes": [ 369 | "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f", 370 | "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c", 371 | "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af", 372 | "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4", 373 | "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a", 374 | "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489", 375 | "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213", 376 | "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01", 377 | "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5", 378 | "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361", 379 | "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26", 380 | "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0", 381 | "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4", 382 | "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8", 383 | "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1", 384 | "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7", 385 | "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6", 386 | "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a", 387 | "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd", 388 | "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4", 389 | "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499", 390 | "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183", 391 | "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544", 392 | "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821", 393 | "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501", 394 | "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f", 395 | "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe", 396 | "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f", 397 | "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672", 398 | "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5", 399 | "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2", 400 | "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57", 401 | "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87", 402 | "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0", 403 | "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f", 404 | "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7", 405 | "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed", 406 | "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70", 407 | "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0", 408 | "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f", 409 | "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d", 410 | "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f", 411 | "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d", 412 | "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431", 413 | "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff", 414 | "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf", 415 | "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83", 416 | "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690", 417 | "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587", 418 | "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e", 419 | "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb", 420 | "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3", 421 | "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66", 422 | "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014", 423 | "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35", 424 | "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f", 425 | "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0", 426 | "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449", 427 | "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23", 428 | "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5", 429 | "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd", 430 | "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4", 431 | "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b", 432 | "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558", 433 | "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd", 434 | "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766", 435 | "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a", 436 | "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636", 437 | "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d", 438 | "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590", 439 | "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e", 440 | "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d", 441 | "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c", 442 | "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28", 443 | "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065", 444 | "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca" 445 | ], 446 | "version": "==3.9.1" 447 | }, 448 | "aiosignal": { 449 | "hashes": [ 450 | "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", 451 | "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" 452 | ], 453 | "markers": "python_version >= '3.7'", 454 | "version": "==1.3.1" 455 | }, 456 | "astroid": { 457 | "hashes": [ 458 | "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91", 459 | "sha256:d6e62862355f60e716164082d6b4b041d38e2a8cf1c7cd953ded5108bac8ff5c" 460 | ], 461 | "markers": "python_full_version >= '3.8.0'", 462 | "version": "==3.0.2" 463 | }, 464 | "attrs": { 465 | "hashes": [ 466 | "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", 467 | "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" 468 | ], 469 | "markers": "python_version >= '3.7'", 470 | "version": "==23.1.0" 471 | }, 472 | "black": { 473 | "hashes": [ 474 | "sha256:2220c470c22476ca9631337b0daae41be2b215599919b19d576a956ad38aca69", 475 | "sha256:2a12829e372563ffff10c18c7aff1ef274da6afbc7bc8ccdb5fcc8ff84cab43f", 476 | "sha256:3d139b9531e6bb6d129497a46475535d8289dddc861a5b980f908c36597b9817", 477 | "sha256:41c0ce5cbdb701900c166bcca08ac941b64cf1d6967509e3caeab126da0ae0d0", 478 | "sha256:4a159ae57f239f3f1ef6a78784b00c1c617c7bb188cc351b3017b9e0702df11c", 479 | "sha256:4de8ba5825588017f90e63d7a25fc4df33a6342d1f4d628ad76130d8f4488fc6", 480 | "sha256:623efdb54e7290ba75f7b822dfd2d8a47a55e721ae63aab671ccfd46b2ba6c5d", 481 | "sha256:6b594b3ede60182215d258c76de2de64712d2e8424442ff4402276e22684abbe", 482 | "sha256:6e3c74b35ea179bb69440286b81c309a64c34a032746a9eef3399dc3ce671352", 483 | "sha256:87c8165fad00b03d9c1d400b1dd250479792f49d012807ee45162d323d04fc06", 484 | "sha256:88d1c60bac2044a409154e895abb9d74c8ff5d034fb70f3e1f7c3ae96206bc0c", 485 | "sha256:915a6b6b916fc66edec886fc71b60284e447d8fa39d22b879af7ae6efccca90f", 486 | "sha256:a2c977909557439d0f17dc82adaea84e48374950d53416efc0b8451a594d42c3", 487 | "sha256:ac226f37fc429b386d6447df6256dc958c28dd602f86f950072febf886995f80", 488 | "sha256:b03cdf8a4e15929adf47e5e40a0ddeea1d63b65cf59c22553c12417a0c7ccbf4", 489 | "sha256:c86ecd9d3da3d91e96da5f4a43d9c4fe35c5698b0633e91f171ba9468d112a8b", 490 | "sha256:cad114d8673adab76b3602c28c461c613b7be3da28415500e42aed47415eb561", 491 | "sha256:cb0a7ea9aa1c108924e31f1204a1e2534af255dbaa24ecbb8c05f47341a7b6f1", 492 | "sha256:d30a018fc03fd1e83c75d40b8a156ef541d0b56b6403b63754e1cc96889849d9", 493 | "sha256:d47b6530c55c092a9d841a12c8b3ad838bd639bebf6660a3df9dae83d4ab83c1", 494 | "sha256:e8a054dbb8947718820be2ed6953d66b912ec2795f282725efdd08381a11b0d0", 495 | "sha256:ec345caf15ae2c61540812500979e92f2989c6b6d4d13d21bdc82908043b3265" 496 | ], 497 | "index": "pypi", 498 | "markers": "python_version >= '3.8'", 499 | "version": "==24.1a1" 500 | }, 501 | "click": { 502 | "hashes": [ 503 | "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", 504 | "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" 505 | ], 506 | "markers": "python_version >= '3.7'", 507 | "version": "==8.1.7" 508 | }, 509 | "dill": { 510 | "hashes": [ 511 | "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e", 512 | "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03" 513 | ], 514 | "markers": "python_version >= '3.11'", 515 | "version": "==0.3.7" 516 | }, 517 | "frozenlist": { 518 | "hashes": [ 519 | "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", 520 | "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", 521 | "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", 522 | "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", 523 | "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", 524 | "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", 525 | "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", 526 | "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", 527 | "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", 528 | "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", 529 | "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", 530 | "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", 531 | "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", 532 | "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", 533 | "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", 534 | "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", 535 | "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", 536 | "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", 537 | "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", 538 | "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", 539 | "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", 540 | "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", 541 | "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", 542 | "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", 543 | "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", 544 | "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", 545 | "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", 546 | "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", 547 | "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", 548 | "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", 549 | "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", 550 | "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", 551 | "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", 552 | "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", 553 | "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", 554 | "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", 555 | "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", 556 | "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", 557 | "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", 558 | "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", 559 | "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", 560 | "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", 561 | "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", 562 | "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", 563 | "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", 564 | "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", 565 | "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", 566 | "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", 567 | "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", 568 | "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", 569 | "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", 570 | "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", 571 | "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", 572 | "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", 573 | "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", 574 | "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", 575 | "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", 576 | "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", 577 | "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", 578 | "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", 579 | "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", 580 | "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", 581 | "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", 582 | "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", 583 | "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", 584 | "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", 585 | "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", 586 | "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", 587 | "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", 588 | "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", 589 | "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", 590 | "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", 591 | "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", 592 | "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", 593 | "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", 594 | "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", 595 | "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" 596 | ], 597 | "markers": "python_version >= '3.8'", 598 | "version": "==1.4.1" 599 | }, 600 | "idna": { 601 | "hashes": [ 602 | "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", 603 | "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" 604 | ], 605 | "markers": "python_version >= '3.5'", 606 | "version": "==3.6" 607 | }, 608 | "isort": { 609 | "hashes": [ 610 | "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", 611 | "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" 612 | ], 613 | "markers": "python_full_version >= '3.8.0'", 614 | "version": "==5.13.2" 615 | }, 616 | "mccabe": { 617 | "hashes": [ 618 | "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", 619 | "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" 620 | ], 621 | "markers": "python_version >= '3.6'", 622 | "version": "==0.7.0" 623 | }, 624 | "multidict": { 625 | "hashes": [ 626 | "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9", 627 | "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8", 628 | "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03", 629 | "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710", 630 | "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161", 631 | "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664", 632 | "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569", 633 | "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067", 634 | "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313", 635 | "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706", 636 | "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2", 637 | "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636", 638 | "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49", 639 | "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93", 640 | "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603", 641 | "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0", 642 | "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60", 643 | "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4", 644 | "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e", 645 | "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1", 646 | "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60", 647 | "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951", 648 | "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc", 649 | "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe", 650 | "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95", 651 | "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d", 652 | "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8", 653 | "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed", 654 | "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2", 655 | "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775", 656 | "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87", 657 | "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c", 658 | "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2", 659 | "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98", 660 | "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3", 661 | "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe", 662 | "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78", 663 | "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660", 664 | "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176", 665 | "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e", 666 | "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988", 667 | "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c", 668 | "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c", 669 | "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0", 670 | "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449", 671 | "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f", 672 | "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde", 673 | "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5", 674 | "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d", 675 | "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac", 676 | "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a", 677 | "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9", 678 | "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca", 679 | "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11", 680 | "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35", 681 | "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063", 682 | "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b", 683 | "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982", 684 | "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258", 685 | "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1", 686 | "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52", 687 | "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480", 688 | "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7", 689 | "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461", 690 | "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d", 691 | "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc", 692 | "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779", 693 | "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a", 694 | "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547", 695 | "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0", 696 | "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171", 697 | "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf", 698 | "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d", 699 | "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba" 700 | ], 701 | "markers": "python_version >= '3.7'", 702 | "version": "==6.0.4" 703 | }, 704 | "mypy-extensions": { 705 | "hashes": [ 706 | "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", 707 | "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" 708 | ], 709 | "markers": "python_version >= '3.5'", 710 | "version": "==1.0.0" 711 | }, 712 | "packaging": { 713 | "hashes": [ 714 | "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", 715 | "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" 716 | ], 717 | "markers": "python_version >= '3.7'", 718 | "version": "==23.2" 719 | }, 720 | "pathspec": { 721 | "hashes": [ 722 | "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", 723 | "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" 724 | ], 725 | "markers": "python_version >= '3.8'", 726 | "version": "==0.12.1" 727 | }, 728 | "platformdirs": { 729 | "hashes": [ 730 | "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380", 731 | "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420" 732 | ], 733 | "markers": "python_version >= '3.8'", 734 | "version": "==4.1.0" 735 | }, 736 | "pylint": { 737 | "hashes": [ 738 | "sha256:58c2398b0301e049609a8429789ec6edf3aabe9b6c5fec916acd18639c16de8b", 739 | "sha256:7a1585285aefc5165db81083c3e06363a27448f6b467b3b0f30dbd0ac1f73810" 740 | ], 741 | "index": "pypi", 742 | "markers": "python_full_version >= '3.8.0'", 743 | "version": "==3.0.3" 744 | }, 745 | "tomlkit": { 746 | "hashes": [ 747 | "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4", 748 | "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba" 749 | ], 750 | "markers": "python_version >= '3.7'", 751 | "version": "==0.12.3" 752 | }, 753 | "yarl": { 754 | "hashes": [ 755 | "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", 756 | "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", 757 | "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559", 758 | "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", 759 | "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", 760 | "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", 761 | "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4", 762 | "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c", 763 | "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130", 764 | "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136", 765 | "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e", 766 | "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec", 767 | "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7", 768 | "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1", 769 | "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455", 770 | "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", 771 | "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", 772 | "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", 773 | "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", 774 | "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", 775 | "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa", 776 | "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", 777 | "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", 778 | "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", 779 | "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", 780 | "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c", 781 | "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", 782 | "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b", 783 | "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", 784 | "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23", 785 | "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd", 786 | "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27", 787 | "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f", 788 | "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece", 789 | "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434", 790 | "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec", 791 | "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", 792 | "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78", 793 | "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d", 794 | "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863", 795 | "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53", 796 | "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", 797 | "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15", 798 | "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5", 799 | "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b", 800 | "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57", 801 | "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3", 802 | "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1", 803 | "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f", 804 | "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", 805 | "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c", 806 | "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", 807 | "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", 808 | "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b", 809 | "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2", 810 | "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b", 811 | "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", 812 | "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be", 813 | "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e", 814 | "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", 815 | "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", 816 | "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", 817 | "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2", 818 | "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392", 819 | "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91", 820 | "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541", 821 | "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf", 822 | "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", 823 | "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66", 824 | "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575", 825 | "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14", 826 | "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5", 827 | "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", 828 | "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e", 829 | "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551", 830 | "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17", 831 | "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead", 832 | "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", 833 | "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", 834 | "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234", 835 | "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0", 836 | "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7", 837 | "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34", 838 | "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", 839 | "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385", 840 | "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", 841 | "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be", 842 | "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", 843 | "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749", 844 | "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec" 845 | ], 846 | "markers": "python_version >= '3.7'", 847 | "version": "==1.9.4" 848 | } 849 | } 850 | } 851 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text](./img/SQLModel.png) 2 | ## FastAPI + SQLModel Boilerplate App 3 | A RestAPI real world app based on SQLModel [documentation example](https://sqlmodel.tiangolo.com/tutorial/), using [FastAPI](https://fastapi.tiangolo.com/) and [SQLModel](https://sqlmodel.tiangolo.com/) 4 | 5 | 6 | ### Quickstart 7 | 1. Start the App: 8 | - Using Python: 9 | `pipenv run python asgi.py` 10 | 11 | - Using Docker: 12 | `docker build -t sqlmodel-api:latest . && docker run -p 8080:8080 sqlmodel-api:latest` 13 | 14 | 2. Use Openapi at: `http://localhost:8080/#/` 15 | 16 | 17 | ### Running Tests: 18 | While your app is running, open another terminal: 19 | `pytest -v tavern_tests/` 20 | 21 | 22 | ![alt text](./img/SQLModelAPI_openapi.png) -------------------------------------------------------------------------------- /api/app.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | 3 | from fastapi import FastAPI 4 | 5 | from api.config import Settings 6 | from api.database import create_db_and_tables 7 | from api.public import api as public_api 8 | from api.utils.logger import logger_config 9 | from api.utils.mock_data_generator import create_heroes_and_teams 10 | 11 | logger = logger_config(__name__) 12 | 13 | 14 | @asynccontextmanager 15 | async def lifespan(app: FastAPI): 16 | create_db_and_tables() 17 | create_heroes_and_teams() 18 | 19 | logger.info("startup: triggered") 20 | 21 | yield 22 | 23 | logger.info("shutdown: triggered") 24 | 25 | 26 | def create_app(settings: Settings): 27 | app = FastAPI( 28 | title=settings.PROJECT_NAME, 29 | version=settings.VERSION, 30 | docs_url="/", 31 | description=settings.DESCRIPTION, 32 | lifespan=lifespan, 33 | ) 34 | 35 | app.include_router(public_api) 36 | 37 | return app 38 | -------------------------------------------------------------------------------- /api/auth/__init__.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | from fastapi import Depends, HTTPException, status 4 | from fastapi.security import HTTPBasic, HTTPBasicCredentials 5 | 6 | from api.config import settings 7 | 8 | basic_auth = HTTPBasic(auto_error=False) 9 | 10 | 11 | def authent( 12 | credentials: HTTPBasicCredentials = Depends(basic_auth), 13 | ): 14 | if check_basic_auth_creds(credentials): 15 | return True 16 | 17 | raise HTTPException(status_code=403, detail="invalid user/password provided") 18 | 19 | 20 | def check_basic_auth_creds( 21 | credentials: HTTPBasicCredentials = Depends(basic_auth), 22 | ): 23 | correct_username = secrets.compare_digest( 24 | credentials.username, settings.API_USERNAME 25 | ) 26 | correct_password = secrets.compare_digest( 27 | credentials.password, settings.API_PASSWORD 28 | ) 29 | 30 | if correct_username and correct_password: 31 | return True 32 | 33 | return False 34 | -------------------------------------------------------------------------------- /api/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import secrets 3 | from typing import Literal 4 | 5 | from pydantic_settings import BaseSettings 6 | 7 | 8 | class Settings(BaseSettings): 9 | PROJECT_NAME: str = f"SQLModel API - {os.getenv('ENV', 'development').capitalize()}" 10 | DESCRIPTION: str = "A FastAPI + SQLModel production-ready API" 11 | ENV: Literal["development", "staging", "production"] = "development" 12 | VERSION: str = "0.1" 13 | SECRET_KEY: str = secrets.token_urlsafe(32) 14 | DATABASE_URI: str = "sqlite:////Users/anth/dev/fastapi-sqlmodel/database.db" 15 | API_USERNAME: str = "svc_test" 16 | API_PASSWORD: str = "superstrongpassword" 17 | 18 | class Config: 19 | case_sensitive = True 20 | 21 | 22 | settings = Settings() 23 | 24 | 25 | class TestSettings(Settings): 26 | class Config: 27 | case_sensitive = True 28 | 29 | 30 | test_settings = TestSettings() 31 | -------------------------------------------------------------------------------- /api/database.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import Session, SQLModel, create_engine 2 | 3 | from api.config import settings 4 | 5 | connect_args = {"check_same_thread": False} 6 | engine = create_engine(settings.DATABASE_URI, echo=True, connect_args=connect_args) 7 | 8 | 9 | def create_db_and_tables(): 10 | SQLModel.metadata.create_all(engine) 11 | 12 | 13 | def get_session(): 14 | with Session(engine) as session: 15 | yield session 16 | -------------------------------------------------------------------------------- /api/public/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from api.auth import authent 4 | from api.public.health import views as health 5 | from api.public.hero import views as heroes 6 | from api.public.team import views as teams 7 | 8 | api = APIRouter() 9 | 10 | 11 | api.include_router( 12 | health.router, 13 | prefix="/health", 14 | tags=["Health"], 15 | dependencies=[Depends(authent)], 16 | ) 17 | api.include_router( 18 | heroes.router, 19 | prefix="/heroes", 20 | tags=["Heroes"], 21 | dependencies=[Depends(authent)], 22 | ) 23 | api.include_router( 24 | teams.router, 25 | prefix="/teams", 26 | tags=["Teams"], 27 | dependencies=[Depends(authent)], 28 | ) 29 | -------------------------------------------------------------------------------- /api/public/health/crud.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends 2 | from sqlmodel import Session, text 3 | 4 | from api.config import settings 5 | from api.database import get_session 6 | from api.public.health.models import Health, Stats, Status 7 | from api.public.hero.crud import read_heroes 8 | from api.utils.logger import logger_config 9 | 10 | logger = logger_config(__name__) 11 | 12 | 13 | def get_health(db: Session) -> Health: 14 | db_status = health_db(db=db) 15 | logger.info("%s.get_health.db_status: %s", __name__, db_status) 16 | return Health(app_status=Status.OK, db_status=db_status, environment=settings.ENV) 17 | 18 | 19 | def get_stats(db: Session) -> Stats: 20 | stats = Stats(heroes=count_from_db("hero", db), teams=count_from_db("team", db)) 21 | logger.info("%sget_stats: %s", __name__, stats) 22 | return stats 23 | 24 | 25 | def count_from_db(table: str, db: Session = Depends(get_session)): 26 | teams = db.exec(text(f"SELECT COUNT(id) FROM {table};")).one_or_none() 27 | return teams[0] if teams else 0 28 | 29 | 30 | def health_db(db: Session = Depends(get_session)) -> Status: 31 | try: 32 | db.exec(text(f"SELECT COUNT(id) FROM hero;")).one_or_none() 33 | return Status.OK 34 | except Exception as e: 35 | logger.exception(e) 36 | 37 | return Status.KO 38 | -------------------------------------------------------------------------------- /api/public/health/models.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Literal 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class Status(str, Enum): 8 | OK = "OK" 9 | KO = "KO" 10 | 11 | 12 | class Health(BaseModel): 13 | app_status: Status | None 14 | db_status: Status | None 15 | environment: Literal["development", "staging", "production"] | None 16 | 17 | 18 | class Stats(BaseModel): 19 | heroes: int | None 20 | teams: int | None 21 | -------------------------------------------------------------------------------- /api/public/health/views.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, status 2 | from sqlmodel import Session 3 | 4 | from api.database import Session, get_session 5 | from api.public.health.crud import get_health, get_stats 6 | from api.public.health.models import Health, Stats 7 | from api.utils.logger import logger_config 8 | 9 | router = APIRouter() 10 | logger = logger_config(__name__) 11 | 12 | 13 | @router.get( 14 | "", 15 | response_model=Health, 16 | status_code=status.HTTP_200_OK, 17 | responses={200: {"model": Health}}, 18 | ) 19 | def health(db: Session = Depends(get_session)): 20 | return get_health(db=db) 21 | 22 | 23 | @router.get( 24 | "/stats", 25 | response_model=Stats, 26 | status_code=status.HTTP_200_OK, 27 | responses={200: {"model": Stats}}, 28 | ) 29 | def health_stats(db: Session = Depends(get_session)): 30 | return get_stats(db=db) 31 | -------------------------------------------------------------------------------- /api/public/hero/crud.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, HTTPException, status 2 | from sqlmodel import Session, select 3 | 4 | from api.database import get_session 5 | from api.public.hero.models import Hero, HeroCreate, HeroUpdate 6 | 7 | 8 | def create_hero(hero: HeroCreate, db: Session = Depends(get_session)): 9 | hero_to_db = Hero.model_validate(hero) 10 | db.add(hero_to_db) 11 | db.commit() 12 | db.refresh(hero_to_db) 13 | return hero_to_db 14 | 15 | 16 | def read_heroes(offset: int = 0, limit: int = 20, db: Session = Depends(get_session)): 17 | heroes = db.exec(select(Hero).offset(offset).limit(limit)).all() 18 | return heroes 19 | 20 | 21 | def read_hero(hero_id: int, db: Session = Depends(get_session)): 22 | hero = db.get(Hero, hero_id) 23 | if not hero: 24 | raise HTTPException( 25 | status_code=status.HTTP_404_NOT_FOUND, 26 | detail=f"Hero not found with id: {hero_id}", 27 | ) 28 | return hero 29 | 30 | 31 | def update_hero(hero_id: int, hero: HeroUpdate, db: Session = Depends(get_session)): 32 | hero_to_update = db.get(Hero, hero_id) 33 | if not hero_to_update: 34 | raise HTTPException( 35 | status_code=status.HTTP_404_NOT_FOUND, 36 | detail=f"Hero not found with id: {hero_id}", 37 | ) 38 | 39 | team_data = hero.model_dump(exclude_unset=True) 40 | for key, value in team_data.items(): 41 | setattr(hero_to_update, key, value) 42 | 43 | db.add(hero_to_update) 44 | db.commit() 45 | db.refresh(hero_to_update) 46 | return hero_to_update 47 | 48 | 49 | def delete_hero(hero_id: int, db: Session = Depends(get_session)): 50 | hero = db.get(Hero, hero_id) 51 | if not hero: 52 | raise HTTPException( 53 | status_code=status.HTTP_404_NOT_FOUND, 54 | detail=f"Hero not found with id: {hero_id}", 55 | ) 56 | 57 | db.delete(hero) 58 | db.commit() 59 | return {"ok": True} 60 | -------------------------------------------------------------------------------- /api/public/hero/models.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import Field, Relationship, SQLModel 2 | 3 | from api.public.team.models import Team 4 | from api.utils.generic_models import HeroTeamLink 5 | 6 | 7 | class HeroBase(SQLModel): 8 | name: str 9 | secret_name: str 10 | age: int | None = None 11 | 12 | class Config: 13 | json_schema_extra = { 14 | "example": { 15 | "id": 1, 16 | "name": "Super Man", 17 | "secret_name": "Clark Kent", 18 | "age": 27, 19 | "team_id": 1, 20 | } 21 | } 22 | 23 | 24 | class Hero(HeroBase, table=True): 25 | id: int | None = Field(default=None, primary_key=True) 26 | teams: list[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink) 27 | 28 | 29 | class HeroCreate(HeroBase): 30 | pass 31 | 32 | 33 | class HeroRead(HeroBase): 34 | id: int 35 | name: str | None = None 36 | secret_name: str | None = None 37 | age: int | None = None 38 | teams: list[Team] = None 39 | 40 | 41 | class HeroUpdate(HeroBase): 42 | name: str | None = None 43 | secret_name: str | None = None 44 | age: int | None = None 45 | teams: list[Team] = None 46 | 47 | class Config: 48 | json_schema_extra = { 49 | "example": { 50 | "name": "Super Man", 51 | "secret_name": "Clark Kent", 52 | "age": 27, 53 | "team_id": 1, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /api/public/hero/views.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, Query 2 | from sqlmodel import Session 3 | 4 | from api.database import get_session 5 | from api.public.hero.crud import ( 6 | create_hero, 7 | delete_hero, 8 | read_hero, 9 | read_heroes, 10 | update_hero, 11 | ) 12 | from api.public.hero.models import HeroCreate, HeroRead, HeroUpdate 13 | 14 | router = APIRouter() 15 | 16 | 17 | @router.post("", response_model=HeroRead) 18 | def create_a_hero(hero: HeroCreate, db: Session = Depends(get_session)): 19 | return create_hero(hero=hero, db=db) 20 | 21 | 22 | @router.get("", response_model=list[HeroRead]) 23 | def get_heroes( 24 | offset: int = 0, 25 | limit: int = Query(default=100, lte=100), 26 | db: Session = Depends(get_session), 27 | ): 28 | return read_heroes(offset=offset, limit=limit, db=db) 29 | 30 | 31 | @router.get("/{hero_id}", response_model=HeroRead) 32 | def get_a_hero(hero_id: int, db: Session = Depends(get_session)): 33 | return read_hero(hero_id=hero_id, db=db) 34 | 35 | 36 | @router.patch("/{hero_id}", response_model=HeroRead) 37 | def update_a_hero(hero_id: int, hero: HeroUpdate, db: Session = Depends(get_session)): 38 | return update_hero(hero_id=hero_id, hero=hero, db=db) 39 | 40 | 41 | @router.delete("/{hero_id}") 42 | def delete_a_hero(hero_id: int, db: Session = Depends(get_session)): 43 | return delete_hero(hero_id=hero_id, db=db) 44 | -------------------------------------------------------------------------------- /api/public/team/crud.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, HTTPException, status 2 | from sqlmodel import Session, select, text 3 | 4 | from api.database import get_session 5 | from api.public.team.models import Team, TeamCreate, TeamUpdate 6 | 7 | 8 | def create_team(team: TeamCreate, db: Session = Depends(get_session)): 9 | team_to_db = Team.model_validate(team) 10 | db.add(team_to_db) 11 | db.commit() 12 | db.refresh(team_to_db) 13 | return team_to_db 14 | 15 | 16 | def read_teams(offset: int = 0, limit: int = 20, db: Session = Depends(get_session)): 17 | teams = db.exec(select(Team).offset(offset).limit(limit)).all() 18 | return teams 19 | 20 | 21 | def read_team(team_id: int, db: Session = Depends(get_session)): 22 | team = db.get(Team, team_id) 23 | if not team: 24 | raise HTTPException( 25 | status_code=status.HTTP_404_NOT_FOUND, 26 | detail=f"Team not found with id: {team_id}", 27 | ) 28 | return team 29 | 30 | 31 | def update_team(team_id: int, team: TeamUpdate, db: Session = Depends(get_session)): 32 | team_to_update = db.get(Team, team_id) 33 | if not team_to_update: 34 | raise HTTPException( 35 | status_code=status.HTTP_404_NOT_FOUND, 36 | detail=f"Team not found with id: {team_id}", 37 | ) 38 | 39 | team_data = team.model_dump(exclude_unset=True) 40 | for key, value in team_data.items(): 41 | setattr(team_to_update, key, value) 42 | 43 | db.add(team_to_update) 44 | db.commit() 45 | db.refresh(team_to_update) 46 | return team_to_update 47 | 48 | 49 | def delete_team(team_id: int, db: Session = Depends(get_session)): 50 | team = db.get(Team, team_id) 51 | if not team: 52 | raise HTTPException( 53 | status_code=status.HTTP_404_NOT_FOUND, 54 | detail=f"Team not found with id: {team_id}", 55 | ) 56 | 57 | db.delete(team) 58 | db.commit() 59 | return {"ok": True} 60 | -------------------------------------------------------------------------------- /api/public/team/models.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import Field, Relationship, SQLModel 2 | 3 | from api.utils.generic_models import HeroTeamLink 4 | 5 | 6 | class TeamBase(SQLModel): 7 | name: str 8 | headquarters: str 9 | 10 | class Config: 11 | json_schema_extra = { 12 | "example": { 13 | "name": "wonderful league", 14 | "headquarters": "Fortress of Solitude", 15 | } 16 | } 17 | 18 | 19 | class Team(TeamBase, table=True): 20 | id: int | None = Field(default=None, primary_key=True) 21 | 22 | heroes: list["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink) # type: ignore 23 | 24 | 25 | class TeamCreate(TeamBase): 26 | pass 27 | 28 | 29 | class TeamRead(TeamBase): 30 | id: int 31 | name: str | None = None 32 | headquarters: str | None = None 33 | heroes: list | None = None 34 | 35 | 36 | class TeamUpdate(TeamBase): 37 | name: str | None = None 38 | headquarters: str | None = None 39 | -------------------------------------------------------------------------------- /api/public/team/views.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, Query 2 | from sqlmodel import Session 3 | 4 | from api.database import get_session 5 | from api.public.team.crud import ( 6 | create_team, 7 | delete_team, 8 | read_team, 9 | read_teams, 10 | update_team, 11 | ) 12 | from api.public.team.models import TeamCreate, TeamRead, TeamUpdate 13 | from api.utils.logger import logger_config 14 | 15 | router = APIRouter() 16 | 17 | logger = logger_config(__name__) 18 | 19 | 20 | @router.post("", response_model=TeamRead) 21 | def create_a_team(team: TeamCreate, db: Session = Depends(get_session)): 22 | logger.info("%s.create_a_team: %s", __name__, team) 23 | return create_team(team=team, db=db) 24 | 25 | 26 | @router.get("", response_model=list[TeamRead]) 27 | def get_teams( 28 | offset: int = 0, 29 | limit: int = Query(default=100, lte=100), 30 | db: Session = Depends(get_session), 31 | ): 32 | logger.info("%s.get_teams: triggered", __name__) 33 | return read_teams(offset=offset, limit=limit, db=db) 34 | 35 | 36 | @router.get("/{team_id}", response_model=TeamRead) 37 | def get_a_team(team_id: int, db: Session = Depends(get_session)): 38 | logger.info("%s.get_a_team.id: %s", __name__, team_id) 39 | return read_team(team_id=team_id, db=db) 40 | 41 | 42 | @router.patch("/{team_id}", response_model=TeamRead) 43 | def update_a_team(team_id: int, team: TeamUpdate, db: Session = Depends(get_session)): 44 | logger.info("%s.update_a_team.id: %s", __name__, team_id) 45 | return update_team(team_id=team_id, team=team, db=db) 46 | 47 | 48 | @router.delete("/{team_id}") 49 | def delete_a_team(team_id: int, db: Session = Depends(get_session)): 50 | logger.info("%s.delete_a_team: %s triggered", __name__, team_id) 51 | return delete_team(team_id=team_id, db=db) 52 | -------------------------------------------------------------------------------- /api/utils/generic_models.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from sqlmodel import Field, SQLModel 4 | 5 | 6 | class HeroTeamLink(SQLModel, table=True): 7 | team_id: int | None = Field(default=None, foreign_key="team.id", primary_key=True) 8 | hero_id: int | None = Field(default=None, foreign_key="hero.id", primary_key=True) 9 | -------------------------------------------------------------------------------- /api/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def logger_config(module): 5 | """ 6 | Logger function. Extends Python loggin module and set a custom config. 7 | params: Module Name. e.i: logger_config(__name__). 8 | return: Custom logger_config Object. 9 | """ 10 | formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") 11 | handler = logging.StreamHandler() 12 | handler.setFormatter(formatter) 13 | 14 | custom_logger = logging.getLogger(module) 15 | custom_logger.setLevel(logging.DEBUG) 16 | 17 | custom_logger.addHandler(handler) 18 | 19 | return custom_logger 20 | -------------------------------------------------------------------------------- /api/utils/mock_data_generator.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import Session 2 | 3 | from api.database import engine 4 | from api.public.hero.models import Hero 5 | from api.public.team.models import Team 6 | from api.utils.logger import logger_config 7 | 8 | logger = logger_config(__name__) 9 | 10 | 11 | def create_heroes_and_teams(): 12 | with Session(engine) as session: 13 | team_preventers = Team(name="Preventers", headquarters="Sharp Tower") 14 | team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar") 15 | wornderful_league = Team( 16 | name="Wonderful-League", headquarters="Fortress of Solitude" 17 | ) 18 | 19 | hero_deadpond = Hero( 20 | name="Deadpond", 21 | secret_name="Dive Wilson", 22 | age=24, 23 | teams=[team_z_force, team_preventers], 24 | ) 25 | hero_rusty_man = Hero( 26 | name="Rusty-Man", 27 | secret_name="Tommy Sharp", 28 | age=48, 29 | teams=[team_preventers], 30 | ) 31 | hero_spider_boy = Hero( 32 | name="Spider-Boy", 33 | secret_name="Pedro Parqueador", 34 | age=37, 35 | teams=[team_preventers], 36 | ) 37 | hero_super_good_boy = Hero( 38 | name="Super-Good-Boy", 39 | secret_name="John Goodman", 40 | age=30, 41 | teams=[wornderful_league, team_z_force], 42 | ) 43 | 44 | session.add(hero_deadpond) 45 | session.add(hero_rusty_man) 46 | session.add(hero_spider_boy) 47 | session.add(hero_super_good_boy) 48 | session.commit() 49 | 50 | session.refresh(hero_deadpond) 51 | session.refresh(hero_rusty_man) 52 | session.refresh(hero_spider_boy) 53 | session.refresh(hero_super_good_boy) 54 | 55 | logger.info("=========== MOCK DATA CREATED ===========") 56 | logger.info("Deadpond %s", hero_deadpond) 57 | logger.info("Deadpond teams %s", hero_deadpond.teams) 58 | logger.info("Rusty-Man %s", hero_rusty_man) 59 | logger.info("Rusty-Man Teams %s", hero_rusty_man.teams) 60 | logger.info("Spider-Boy %s", hero_spider_boy) 61 | logger.info("Spider-Boy Teams %s", hero_spider_boy.teams) 62 | logger.info("Super-Good-Boy %s", hero_super_good_boy) 63 | logger.info("Super-Good-Boy Teams: %s", hero_super_good_boy.teams) 64 | logger.info("===========================================") 65 | -------------------------------------------------------------------------------- /asgi.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | from api.app import create_app 4 | from api.config import settings 5 | 6 | api = create_app(settings) 7 | 8 | if __name__ == "__main__": 9 | uvicorn.run("asgi:api", host="0.0.0.0", port=8080, reload=True) 10 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec "$@" -------------------------------------------------------------------------------- /img/SQLModel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonycepeda/fastapi-sqlmodel/4b4b039e5d5ba33e8b5d3078b2b77736b516e646/img/SQLModel.png -------------------------------------------------------------------------------- /img/SQLModelAPI_openapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonycepeda/fastapi-sqlmodel/4b4b039e5d5ba33e8b5d3078b2b77736b516e646/img/SQLModelAPI_openapi.png -------------------------------------------------------------------------------- /tavern_tests/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "FastAPI - SQLModel API" 3 | description: Common Settings 4 | 5 | variables: 6 | host: http://localhost:8080 7 | hero_id: 1 8 | hero_name: "Super Man" 9 | hero_secret_name: "Clark Kent" 10 | hero_age: 27 11 | team_id: 1 12 | team_name: "wonderful league" 13 | team_headquarters: "Fortress of Solitude" -------------------------------------------------------------------------------- /tavern_tests/test_hero.tavern.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | test_name: Hero Tests 3 | includes: 4 | - !include common.yaml 5 | 6 | stages: 7 | - name: Create a Hero 8 | request: 9 | method: POST 10 | url: "{host}/heroes" 11 | json: 12 | id: 1 13 | name: "Super Man" 14 | secret_name: "Clark Kent" 15 | age: 27 16 | team_id: 1 17 | response: 18 | status_code: 200 19 | json: {} 20 | 21 | - name: Get a Hero 22 | request: 23 | method: GET 24 | url: "{host}/heroes/1" 25 | response: 26 | status_code: 200 27 | json: [] 28 | 29 | - name: Get a Hero - Not Found 30 | request: 31 | method: GET 32 | url: "{host}/heroes/99" 33 | response: 34 | status_code: 404 35 | json: 36 | detail: "Hero not found with id: 99" 37 | 38 | - name: Get a Heroes 39 | request: 40 | method: GET 41 | url: "{host}/heroes" 42 | params: 43 | offset: 0 44 | limit: 100 45 | response: 46 | status_code: 200 47 | json: [] 48 | 49 | - name: Update a Heroe 50 | request: 51 | method: PATCH 52 | url: "{host}/heroes/1" 53 | json: 54 | name: "Super Man" 55 | secret_name: "Clark Kent" 56 | age: 28 57 | team_id: 1 58 | response: 59 | status_code: 200 60 | json: 61 | id: 1 62 | name: "Super Man" 63 | secret_name: "Clark Kent" 64 | age: 28 65 | team_id: 1 66 | 67 | - name: Delete a Heroe 68 | request: 69 | method: DELETE 70 | url: "{host}/heroes/1" 71 | response: 72 | json: 73 | ok: true -------------------------------------------------------------------------------- /tavern_tests/test_team.tavern.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | test_name: Team Tests 3 | includes: 4 | - !include common.yaml 5 | 6 | stages: 7 | - name: Create a Team 8 | request: 9 | method: POST 10 | url: "{host}/teams" 11 | json: 12 | name: "wonderful league" 13 | headquarters: "Fortress of Solitude" 14 | response: 15 | status_code: 200 16 | json: {} 17 | 18 | - name: Get a Team 19 | request: 20 | method: GET 21 | url: "{host}/teams/1" 22 | response: 23 | status_code: 200 24 | json: [] 25 | 26 | - name: Get a Team - Not Found 27 | request: 28 | method: GET 29 | url: "{host}/teams/99" 30 | response: 31 | status_code: 404 32 | json: 33 | detail: "Team not found with id: 99" 34 | 35 | - name: Get Teams 36 | request: 37 | method: GET 38 | url: "{host}/teams" 39 | params: 40 | offset: 0 41 | limit: 100 42 | response: 43 | status_code: 200 44 | json: [] 45 | 46 | - name: Update a Teame 47 | request: 48 | method: PATCH 49 | url: "{host}/teams/1" 50 | json: 51 | name: "wonderful league" 52 | headquarters: "frozen tundra" 53 | response: 54 | status_code: 200 55 | json: 56 | id: 1 57 | name: "wonderful league" 58 | headquarters: "frozen tundra" 59 | 60 | - name: Delete a Teame 61 | request: 62 | method: DELETE 63 | url: "{host}/teams/1" 64 | response: 65 | json: 66 | ok: true --------------------------------------------------------------------------------