├── .dockerignore ├── .flake8 ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── Dockerfile ├── README.md ├── happy.jpg ├── k8s └── deployment.yaml ├── poetry.lock ├── pyproject.toml ├── streamfinity_fastapi ├── __init__.py ├── db.py ├── db │ └── streamfinity.db ├── routers │ ├── __init__.py │ ├── actors.py │ ├── movies.py │ ├── subscriptions.py │ ├── token.py │ └── users.py ├── schemas │ ├── __init__.py │ ├── movie_actor_schema.py │ ├── subscription_schema.py │ ├── token_schema.py │ └── user_schema.py ├── security │ ├── __init__.py │ └── hashing.py └── streamfinity.py └── tests ├── __init__.py ├── conftest.py ├── db └── streamfinity_test.db ├── test_actors_api.py ├── test_movies_api.py ├── test_subscriptions.py └── test_users_api.py /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | *.egg-info 6 | *.github 7 | *.git 8 | *.test 9 | dist 10 | build -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 90 3 | count = true -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Python CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build-and-deploy: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v3 21 | with: 22 | python-version: 3.11.3 23 | 24 | - name: Install poetry 25 | run: | 26 | python -m pip install poetry==1.4.2 27 | 28 | - name: Configure poetry 29 | run: | 30 | python -m poetry config virtualenvs.in-project true 31 | 32 | - name: Cache the virtualenv 33 | uses: actions/cache@v3 34 | with: 35 | path: ./.venv 36 | key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} 37 | 38 | - name: Install dependencies 39 | run: | 40 | python -m poetry install 41 | 42 | - name: Install semver 43 | run: | 44 | python -m pip install semver 45 | 46 | - name: Get current version 47 | id: get_version 48 | run: echo "::set-output name=version::$(grep -oP '(?<=^ENV VERSION ).*' Dockerfile)" 49 | 50 | - name: Increment version 51 | id: increment_version 52 | run: echo "::set-output name=version::$(python -c "import semver; print(semver.bump_patch('${{ steps.get_version.outputs.version }}'))")" 53 | 54 | - name: Update Dockerfile 55 | run: sed -i "s/ENV VERSION .*/ENV VERSION ${{ steps.increment_version.outputs.version }}/g" Dockerfile 56 | 57 | - name: Build Docker image 58 | run: | 59 | docker build -t pkalkman/streamfinity-api:${{ steps.increment_version.outputs.version }} . 60 | docker tag pkalkman/streamfinity-api:${{ steps.increment_version.outputs.version }} pkalkman/streamfinity-api:latest 61 | 62 | - name: Log in to Docker Hub 63 | uses: docker/login-action@v2 64 | with: 65 | username: ${{ secrets.DOCKER_USERNAME }} 66 | password: ${{ secrets.DOCKER_PASSWORD }} 67 | 68 | - name: Push Docker image 69 | run: | 70 | docker push pkalkman/streamfinity-api:${{ steps.increment_version.outputs.version }} 71 | docker push pkalkman/streamfinity-api:latest 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | **/.DS_Store 7 | 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 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 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 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine as requirements-stage 2 | 3 | ENV VERSION 1.0.0 4 | 5 | WORKDIR /tmp 6 | 7 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev && \ 8 | pip install poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 12 | 13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 14 | 15 | FROM python:3.11-alpine 16 | 17 | # Create a non-root user 18 | RUN adduser --disabled-password --gecos '' appuser 19 | 20 | WORKDIR /streamfinity_fastapi 21 | 22 | COPY --from=requirements-stage /tmp/requirements.txt /streamfinity_fastapi/requirements.txt 23 | 24 | RUN apk add --no-cache libffi openssl && \ 25 | pip install --no-cache-dir --upgrade -r /streamfinity_fastapi/requirements.txt && \ 26 | rm -rf /root/.cache && \ 27 | rm -rf /var/cache/apk/* 28 | 29 | COPY ./streamfinity_fastapi /streamfinity_fastapi/ 30 | 31 | # Change ownership of the app directory to the non-root user 32 | RUN chown -R appuser:appuser /streamfinity_fastapi 33 | 34 | USER appuser 35 | 36 | WORKDIR / 37 | 38 | ENV PORT=8000 39 | 40 | CMD ["uvicorn", "streamfinity_fastapi.streamfinity:app", "--host", "0.0.0.0", "--port", "${PORT}"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # streamfinity-fastapi 2 | A repository that contains the Streamfinity's prototype REST API that go with [a Medium article called Streamfinity's API Makeover - Embracing Python and FastAPI](https://medium.com/itnext/streamfinitys-api-makeover-embracing-python-and-fastapi-93df4dc237aa) 3 | 4 | ![Streamfinity's Harper Celebrating](/happy.jpg "Streamfinity's Harper Celebrating") 5 | -------------------------------------------------------------------------------- /happy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/happy.jpg -------------------------------------------------------------------------------- /k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: streamfinity-api 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: streamfinity-api 10 | template: 11 | metadata: 12 | labels: 13 | app: streamfinity-api 14 | spec: 15 | containers: 16 | - name: streamfinity-api 17 | image: streamfinity-api:v1.0.0-beta 18 | ports: 19 | - containerPort: 8000 -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "anyio" 5 | version = "3.6.2" 6 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.6.2" 10 | files = [ 11 | {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, 12 | {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, 13 | ] 14 | 15 | [package.dependencies] 16 | idna = ">=2.8" 17 | sniffio = ">=1.1" 18 | 19 | [package.extras] 20 | doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 21 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] 22 | trio = ["trio (>=0.16,<0.22)"] 23 | 24 | [[package]] 25 | name = "bcrypt" 26 | version = "4.0.1" 27 | description = "Modern password hashing for your software and your servers" 28 | category = "main" 29 | optional = false 30 | python-versions = ">=3.6" 31 | files = [ 32 | {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, 33 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, 34 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, 35 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, 36 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, 37 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, 38 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, 39 | {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, 40 | {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, 41 | {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, 42 | {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, 43 | {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, 44 | {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, 45 | {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, 46 | {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, 47 | {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, 48 | {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, 49 | {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, 50 | {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, 51 | {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, 52 | {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, 53 | ] 54 | 55 | [package.extras] 56 | tests = ["pytest (>=3.2.1,!=3.3.0)"] 57 | typecheck = ["mypy"] 58 | 59 | [[package]] 60 | name = "certifi" 61 | version = "2023.5.7" 62 | description = "Python package for providing Mozilla's CA Bundle." 63 | category = "main" 64 | optional = false 65 | python-versions = ">=3.6" 66 | files = [ 67 | {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, 68 | {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, 69 | ] 70 | 71 | [[package]] 72 | name = "click" 73 | version = "8.1.3" 74 | description = "Composable command line interface toolkit" 75 | category = "main" 76 | optional = false 77 | python-versions = ">=3.7" 78 | files = [ 79 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 80 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 81 | ] 82 | 83 | [package.dependencies] 84 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 85 | 86 | [[package]] 87 | name = "colorama" 88 | version = "0.4.6" 89 | description = "Cross-platform colored terminal text." 90 | category = "main" 91 | optional = false 92 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 93 | files = [ 94 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 95 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 96 | ] 97 | 98 | [[package]] 99 | name = "ecdsa" 100 | version = "0.18.0" 101 | description = "ECDSA cryptographic signature library (pure python)" 102 | category = "main" 103 | optional = false 104 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 105 | files = [ 106 | {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, 107 | {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, 108 | ] 109 | 110 | [package.dependencies] 111 | six = ">=1.9.0" 112 | 113 | [package.extras] 114 | gmpy = ["gmpy"] 115 | gmpy2 = ["gmpy2"] 116 | 117 | [[package]] 118 | name = "fastapi" 119 | version = "0.95.1" 120 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 121 | category = "main" 122 | optional = false 123 | python-versions = ">=3.7" 124 | files = [ 125 | {file = "fastapi-0.95.1-py3-none-any.whl", hash = "sha256:a870d443e5405982e1667dfe372663abf10754f246866056336d7f01c21dab07"}, 126 | {file = "fastapi-0.95.1.tar.gz", hash = "sha256:9569f0a381f8a457ec479d90fa01005cfddaae07546eb1f3fa035bc4797ae7d5"}, 127 | ] 128 | 129 | [package.dependencies] 130 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 131 | starlette = ">=0.26.1,<0.27.0" 132 | 133 | [package.extras] 134 | all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] 135 | dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"] 136 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"] 137 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] 138 | 139 | [[package]] 140 | name = "flake8" 141 | version = "6.0.0" 142 | description = "the modular source code checker: pep8 pyflakes and co" 143 | category = "main" 144 | optional = false 145 | python-versions = ">=3.8.1" 146 | files = [ 147 | {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, 148 | {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, 149 | ] 150 | 151 | [package.dependencies] 152 | mccabe = ">=0.7.0,<0.8.0" 153 | pycodestyle = ">=2.10.0,<2.11.0" 154 | pyflakes = ">=3.0.0,<3.1.0" 155 | 156 | [[package]] 157 | name = "greenlet" 158 | version = "2.0.2" 159 | description = "Lightweight in-process concurrent programming" 160 | category = "main" 161 | optional = false 162 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" 163 | files = [ 164 | {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, 165 | {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, 166 | {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, 167 | {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, 168 | {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, 169 | {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, 170 | {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, 171 | {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, 172 | {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, 173 | {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, 174 | {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, 175 | {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, 176 | {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, 177 | {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, 178 | {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, 179 | {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, 180 | {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, 181 | {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, 182 | {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, 183 | {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, 184 | {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, 185 | {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, 186 | {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, 187 | {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, 188 | {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, 189 | {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, 190 | {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, 191 | {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, 192 | {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, 193 | {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, 194 | {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, 195 | {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, 196 | {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, 197 | {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, 198 | {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, 199 | {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, 200 | {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, 201 | {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, 202 | {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, 203 | {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, 204 | {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, 205 | {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, 206 | {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, 207 | {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, 208 | {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, 209 | {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, 210 | {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, 211 | {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, 212 | {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, 213 | {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, 214 | {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, 215 | {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, 216 | {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, 217 | {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, 218 | {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, 219 | {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, 220 | {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, 221 | {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, 222 | {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, 223 | {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, 224 | ] 225 | 226 | [package.extras] 227 | docs = ["Sphinx", "docutils (<0.18)"] 228 | test = ["objgraph", "psutil"] 229 | 230 | [[package]] 231 | name = "gunicorn" 232 | version = "20.1.0" 233 | description = "WSGI HTTP Server for UNIX" 234 | category = "main" 235 | optional = false 236 | python-versions = ">=3.5" 237 | files = [ 238 | {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, 239 | {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, 240 | ] 241 | 242 | [package.dependencies] 243 | setuptools = ">=3.0" 244 | 245 | [package.extras] 246 | eventlet = ["eventlet (>=0.24.1)"] 247 | gevent = ["gevent (>=1.4.0)"] 248 | setproctitle = ["setproctitle"] 249 | tornado = ["tornado (>=0.2)"] 250 | 251 | [[package]] 252 | name = "h11" 253 | version = "0.14.0" 254 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 255 | category = "main" 256 | optional = false 257 | python-versions = ">=3.7" 258 | files = [ 259 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 260 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 261 | ] 262 | 263 | [[package]] 264 | name = "httpcore" 265 | version = "0.17.0" 266 | description = "A minimal low-level HTTP client." 267 | category = "main" 268 | optional = false 269 | python-versions = ">=3.7" 270 | files = [ 271 | {file = "httpcore-0.17.0-py3-none-any.whl", hash = "sha256:0fdfea45e94f0c9fd96eab9286077f9ff788dd186635ae61b312693e4d943599"}, 272 | {file = "httpcore-0.17.0.tar.gz", hash = "sha256:cc045a3241afbf60ce056202301b4d8b6af08845e3294055eb26b09913ef903c"}, 273 | ] 274 | 275 | [package.dependencies] 276 | anyio = ">=3.0,<5.0" 277 | certifi = "*" 278 | h11 = ">=0.13,<0.15" 279 | sniffio = ">=1.0.0,<2.0.0" 280 | 281 | [package.extras] 282 | http2 = ["h2 (>=3,<5)"] 283 | socks = ["socksio (>=1.0.0,<2.0.0)"] 284 | 285 | [[package]] 286 | name = "httptools" 287 | version = "0.5.0" 288 | description = "A collection of framework independent HTTP protocol utils." 289 | category = "main" 290 | optional = false 291 | python-versions = ">=3.5.0" 292 | files = [ 293 | {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, 294 | {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, 295 | {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, 296 | {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, 297 | {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, 298 | {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, 299 | {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, 300 | {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, 301 | {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, 302 | {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, 303 | {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, 304 | {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, 305 | {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, 306 | {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, 307 | {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, 308 | {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, 309 | {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, 310 | {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, 311 | {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, 312 | {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, 313 | {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, 314 | {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, 315 | {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, 316 | {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, 317 | {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, 318 | {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, 319 | {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, 320 | {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, 321 | {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, 322 | {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, 323 | {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, 324 | {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, 325 | {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, 326 | {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, 327 | {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, 328 | {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, 329 | {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, 330 | {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, 331 | {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, 332 | {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, 333 | {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, 334 | ] 335 | 336 | [package.extras] 337 | test = ["Cython (>=0.29.24,<0.30.0)"] 338 | 339 | [[package]] 340 | name = "httpx" 341 | version = "0.24.0" 342 | description = "The next generation HTTP client." 343 | category = "main" 344 | optional = false 345 | python-versions = ">=3.7" 346 | files = [ 347 | {file = "httpx-0.24.0-py3-none-any.whl", hash = "sha256:447556b50c1921c351ea54b4fe79d91b724ed2b027462ab9a329465d147d5a4e"}, 348 | {file = "httpx-0.24.0.tar.gz", hash = "sha256:507d676fc3e26110d41df7d35ebd8b3b8585052450f4097401c9be59d928c63e"}, 349 | ] 350 | 351 | [package.dependencies] 352 | certifi = "*" 353 | httpcore = ">=0.15.0,<0.18.0" 354 | idna = "*" 355 | sniffio = "*" 356 | 357 | [package.extras] 358 | brotli = ["brotli", "brotlicffi"] 359 | cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] 360 | http2 = ["h2 (>=3,<5)"] 361 | socks = ["socksio (>=1.0.0,<2.0.0)"] 362 | 363 | [[package]] 364 | name = "idna" 365 | version = "3.4" 366 | description = "Internationalized Domain Names in Applications (IDNA)" 367 | category = "main" 368 | optional = false 369 | python-versions = ">=3.5" 370 | files = [ 371 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 372 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 373 | ] 374 | 375 | [[package]] 376 | name = "iniconfig" 377 | version = "2.0.0" 378 | description = "brain-dead simple config-ini parsing" 379 | category = "main" 380 | optional = false 381 | python-versions = ">=3.7" 382 | files = [ 383 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 384 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 385 | ] 386 | 387 | [[package]] 388 | name = "mccabe" 389 | version = "0.7.0" 390 | description = "McCabe checker, plugin for flake8" 391 | category = "main" 392 | optional = false 393 | python-versions = ">=3.6" 394 | files = [ 395 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 396 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 397 | ] 398 | 399 | [[package]] 400 | name = "packaging" 401 | version = "23.1" 402 | description = "Core utilities for Python packages" 403 | category = "main" 404 | optional = false 405 | python-versions = ">=3.7" 406 | files = [ 407 | {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, 408 | {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, 409 | ] 410 | 411 | [[package]] 412 | name = "passlib" 413 | version = "1.7.4" 414 | description = "comprehensive password hashing framework supporting over 30 schemes" 415 | category = "main" 416 | optional = false 417 | python-versions = "*" 418 | files = [ 419 | {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, 420 | {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, 421 | ] 422 | 423 | [package.extras] 424 | argon2 = ["argon2-cffi (>=18.2.0)"] 425 | bcrypt = ["bcrypt (>=3.1.0)"] 426 | build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] 427 | totp = ["cryptography"] 428 | 429 | [[package]] 430 | name = "pluggy" 431 | version = "1.0.0" 432 | description = "plugin and hook calling mechanisms for python" 433 | category = "main" 434 | optional = false 435 | python-versions = ">=3.6" 436 | files = [ 437 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 438 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 439 | ] 440 | 441 | [package.extras] 442 | dev = ["pre-commit", "tox"] 443 | testing = ["pytest", "pytest-benchmark"] 444 | 445 | [[package]] 446 | name = "pyasn1" 447 | version = "0.5.0" 448 | description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" 449 | category = "main" 450 | optional = false 451 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 452 | files = [ 453 | {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, 454 | {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, 455 | ] 456 | 457 | [[package]] 458 | name = "pycodestyle" 459 | version = "2.10.0" 460 | description = "Python style guide checker" 461 | category = "main" 462 | optional = false 463 | python-versions = ">=3.6" 464 | files = [ 465 | {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, 466 | {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, 467 | ] 468 | 469 | [[package]] 470 | name = "pydantic" 471 | version = "1.10.7" 472 | description = "Data validation and settings management using python type hints" 473 | category = "main" 474 | optional = false 475 | python-versions = ">=3.7" 476 | files = [ 477 | {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, 478 | {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, 479 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, 480 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, 481 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, 482 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, 483 | {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, 484 | {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, 485 | {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, 486 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, 487 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, 488 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, 489 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, 490 | {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, 491 | {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, 492 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, 493 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, 494 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, 495 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, 496 | {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, 497 | {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, 498 | {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, 499 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, 500 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, 501 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, 502 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, 503 | {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, 504 | {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, 505 | {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, 506 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, 507 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, 508 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, 509 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, 510 | {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, 511 | {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, 512 | {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, 513 | ] 514 | 515 | [package.dependencies] 516 | typing-extensions = ">=4.2.0" 517 | 518 | [package.extras] 519 | dotenv = ["python-dotenv (>=0.10.4)"] 520 | email = ["email-validator (>=1.0.3)"] 521 | 522 | [[package]] 523 | name = "pyflakes" 524 | version = "3.0.1" 525 | description = "passive checker of Python programs" 526 | category = "main" 527 | optional = false 528 | python-versions = ">=3.6" 529 | files = [ 530 | {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, 531 | {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, 532 | ] 533 | 534 | [[package]] 535 | name = "pytest" 536 | version = "7.3.1" 537 | description = "pytest: simple powerful testing with Python" 538 | category = "main" 539 | optional = false 540 | python-versions = ">=3.7" 541 | files = [ 542 | {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, 543 | {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, 544 | ] 545 | 546 | [package.dependencies] 547 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 548 | iniconfig = "*" 549 | packaging = "*" 550 | pluggy = ">=0.12,<2.0" 551 | 552 | [package.extras] 553 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 554 | 555 | [[package]] 556 | name = "python-decouple" 557 | version = "3.8" 558 | description = "Strict separation of settings from code." 559 | category = "main" 560 | optional = false 561 | python-versions = "*" 562 | files = [ 563 | {file = "python-decouple-3.8.tar.gz", hash = "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f"}, 564 | {file = "python_decouple-3.8-py3-none-any.whl", hash = "sha256:d0d45340815b25f4de59c974b855bb38d03151d81b037d9e3f463b0c9f8cbd66"}, 565 | ] 566 | 567 | [[package]] 568 | name = "python-dotenv" 569 | version = "1.0.0" 570 | description = "Read key-value pairs from a .env file and set them as environment variables" 571 | category = "main" 572 | optional = false 573 | python-versions = ">=3.8" 574 | files = [ 575 | {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, 576 | {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, 577 | ] 578 | 579 | [package.extras] 580 | cli = ["click (>=5.0)"] 581 | 582 | [[package]] 583 | name = "python-jose" 584 | version = "3.3.0" 585 | description = "JOSE implementation in Python" 586 | category = "main" 587 | optional = false 588 | python-versions = "*" 589 | files = [ 590 | {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, 591 | {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, 592 | ] 593 | 594 | [package.dependencies] 595 | ecdsa = "!=0.15" 596 | pyasn1 = "*" 597 | rsa = "*" 598 | 599 | [package.extras] 600 | cryptography = ["cryptography (>=3.4.0)"] 601 | pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] 602 | pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] 603 | 604 | [[package]] 605 | name = "python-multipart" 606 | version = "0.0.6" 607 | description = "A streaming multipart parser for Python" 608 | category = "main" 609 | optional = false 610 | python-versions = ">=3.7" 611 | files = [ 612 | {file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"}, 613 | {file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"}, 614 | ] 615 | 616 | [package.extras] 617 | dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] 618 | 619 | [[package]] 620 | name = "pyyaml" 621 | version = "6.0" 622 | description = "YAML parser and emitter for Python" 623 | category = "main" 624 | optional = false 625 | python-versions = ">=3.6" 626 | files = [ 627 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 628 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 629 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 630 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 631 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 632 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 633 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 634 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 635 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 636 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 637 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 638 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 639 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 640 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 641 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 642 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 643 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 644 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 645 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 646 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 647 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 648 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 649 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 650 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 651 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 652 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 653 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 654 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 655 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 656 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 657 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 658 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 659 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 660 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 661 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 662 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 663 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 664 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 665 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 666 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 667 | ] 668 | 669 | [[package]] 670 | name = "rsa" 671 | version = "4.9" 672 | description = "Pure-Python RSA implementation" 673 | category = "main" 674 | optional = false 675 | python-versions = ">=3.6,<4" 676 | files = [ 677 | {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, 678 | {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, 679 | ] 680 | 681 | [package.dependencies] 682 | pyasn1 = ">=0.1.3" 683 | 684 | [[package]] 685 | name = "setuptools" 686 | version = "67.7.2" 687 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 688 | category = "main" 689 | optional = false 690 | python-versions = ">=3.7" 691 | files = [ 692 | {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, 693 | {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, 694 | ] 695 | 696 | [package.extras] 697 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 698 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 699 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 700 | 701 | [[package]] 702 | name = "six" 703 | version = "1.16.0" 704 | description = "Python 2 and 3 compatibility utilities" 705 | category = "main" 706 | optional = false 707 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 708 | files = [ 709 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 710 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 711 | ] 712 | 713 | [[package]] 714 | name = "sniffio" 715 | version = "1.3.0" 716 | description = "Sniff out which async library your code is running under" 717 | category = "main" 718 | optional = false 719 | python-versions = ">=3.7" 720 | files = [ 721 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 722 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 723 | ] 724 | 725 | [[package]] 726 | name = "sqlalchemy" 727 | version = "1.4.41" 728 | description = "Database Abstraction Library" 729 | category = "main" 730 | optional = false 731 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 732 | files = [ 733 | {file = "SQLAlchemy-1.4.41-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:13e397a9371ecd25573a7b90bd037db604331cf403f5318038c46ee44908c44d"}, 734 | {file = "SQLAlchemy-1.4.41-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2d6495f84c4fd11584f34e62f9feec81bf373787b3942270487074e35cbe5330"}, 735 | {file = "SQLAlchemy-1.4.41-cp27-cp27m-win32.whl", hash = "sha256:e570cfc40a29d6ad46c9aeaddbdcee687880940a3a327f2c668dd0e4ef0a441d"}, 736 | {file = "SQLAlchemy-1.4.41-cp27-cp27m-win_amd64.whl", hash = "sha256:5facb7fd6fa8a7353bbe88b95695e555338fb038ad19ceb29c82d94f62775a05"}, 737 | {file = "SQLAlchemy-1.4.41-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f37fa70d95658763254941ddd30ecb23fc4ec0c5a788a7c21034fc2305dab7cc"}, 738 | {file = "SQLAlchemy-1.4.41-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:361f6b5e3f659e3c56ea3518cf85fbdae1b9e788ade0219a67eeaaea8a4e4d2a"}, 739 | {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0990932f7cca97fece8017414f57fdd80db506a045869d7ddf2dda1d7cf69ecc"}, 740 | {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd767cf5d7252b1c88fcfb58426a32d7bd14a7e4942497e15b68ff5d822b41ad"}, 741 | {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5102fb9ee2c258a2218281adcb3e1918b793c51d6c2b4666ce38c35101bb940e"}, 742 | {file = "SQLAlchemy-1.4.41-cp310-cp310-win32.whl", hash = "sha256:2082a2d2fca363a3ce21cfa3d068c5a1ce4bf720cf6497fb3a9fc643a8ee4ddd"}, 743 | {file = "SQLAlchemy-1.4.41-cp310-cp310-win_amd64.whl", hash = "sha256:e4b12e3d88a8fffd0b4ca559f6d4957ed91bd4c0613a4e13846ab8729dc5c251"}, 744 | {file = "SQLAlchemy-1.4.41-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:90484a2b00baedad361402c257895b13faa3f01780f18f4a104a2f5c413e4536"}, 745 | {file = "SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67fc780cfe2b306180e56daaa411dd3186bf979d50a6a7c2a5b5036575cbdbb"}, 746 | {file = "SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad2b727fc41c7f8757098903f85fafb4bf587ca6605f82d9bf5604bd9c7cded"}, 747 | {file = "SQLAlchemy-1.4.41-cp311-cp311-win32.whl", hash = "sha256:59bdc291165b6119fc6cdbc287c36f7f2859e6051dd923bdf47b4c55fd2f8bd0"}, 748 | {file = "SQLAlchemy-1.4.41-cp311-cp311-win_amd64.whl", hash = "sha256:d2e054aed4645f9b755db85bc69fc4ed2c9020c19c8027976f66576b906a74f1"}, 749 | {file = "SQLAlchemy-1.4.41-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4ba7e122510bbc07258dc42be6ed45997efdf38129bde3e3f12649be70683546"}, 750 | {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0dcf127bb99458a9d211e6e1f0f3edb96c874dd12f2503d4d8e4f1fd103790b"}, 751 | {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e16c2be5cb19e2c08da7bd3a87fed2a0d4e90065ee553a940c4fc1a0fb1ab72b"}, 752 | {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebeeec5c14533221eb30bad716bc1fd32f509196318fb9caa7002c4a364e4c"}, 753 | {file = "SQLAlchemy-1.4.41-cp36-cp36m-win32.whl", hash = "sha256:3e2ef592ac3693c65210f8b53d0edcf9f4405925adcfc031ff495e8d18169682"}, 754 | {file = "SQLAlchemy-1.4.41-cp36-cp36m-win_amd64.whl", hash = "sha256:eb30cf008850c0a26b72bd1b9be6730830165ce049d239cfdccd906f2685f892"}, 755 | {file = "SQLAlchemy-1.4.41-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:c23d64a0b28fc78c96289ffbd0d9d1abd48d267269b27f2d34e430ea73ce4b26"}, 756 | {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb8897367a21b578b26f5713833836f886817ee2ffba1177d446fa3f77e67c8"}, 757 | {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14576238a5f89bcf504c5f0a388d0ca78df61fb42cb2af0efe239dc965d4f5c9"}, 758 | {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639e1ae8d48b3c86ffe59c0daa9a02e2bfe17ca3d2b41611b30a0073937d4497"}, 759 | {file = "SQLAlchemy-1.4.41-cp37-cp37m-win32.whl", hash = "sha256:0005bd73026cd239fc1e8ccdf54db58b6193be9a02b3f0c5983808f84862c767"}, 760 | {file = "SQLAlchemy-1.4.41-cp37-cp37m-win_amd64.whl", hash = "sha256:5323252be2bd261e0aa3f33cb3a64c45d76829989fa3ce90652838397d84197d"}, 761 | {file = "SQLAlchemy-1.4.41-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:05f0de3a1dc3810a776275763764bb0015a02ae0f698a794646ebc5fb06fad33"}, 762 | {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0002e829142b2af00b4eaa26c51728f3ea68235f232a2e72a9508a3116bd6ed0"}, 763 | {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ff16cedab5b16a0db79f1bc99e46a6ddececb60c396562e50aab58ddb2871c"}, 764 | {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfd238f766a5bb5ee5545a62dd03f316ac67966a6a658efb63eeff8158a4bbf"}, 765 | {file = "SQLAlchemy-1.4.41-cp38-cp38-win32.whl", hash = "sha256:58bb65b3274b0c8a02cea9f91d6f44d0da79abc993b33bdedbfec98c8440175a"}, 766 | {file = "SQLAlchemy-1.4.41-cp38-cp38-win_amd64.whl", hash = "sha256:ce8feaa52c1640de9541eeaaa8b5fb632d9d66249c947bb0d89dd01f87c7c288"}, 767 | {file = "SQLAlchemy-1.4.41-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:199a73c31ac8ea59937cc0bf3dfc04392e81afe2ec8a74f26f489d268867846c"}, 768 | {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676d51c9f6f6226ae8f26dc83ec291c088fe7633269757d333978df78d931ab"}, 769 | {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:036d8472356e1d5f096c5e0e1a7e0f9182140ada3602f8fff6b7329e9e7cfbcd"}, 770 | {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2307495d9e0ea00d0c726be97a5b96615035854972cc538f6e7eaed23a35886c"}, 771 | {file = "SQLAlchemy-1.4.41-cp39-cp39-win32.whl", hash = "sha256:9c56e19780cd1344fcd362fd6265a15f48aa8d365996a37fab1495cae8fcd97d"}, 772 | {file = "SQLAlchemy-1.4.41-cp39-cp39-win_amd64.whl", hash = "sha256:f5fa526d027d804b1f85cdda1eb091f70bde6fb7d87892f6dd5a48925bc88898"}, 773 | {file = "SQLAlchemy-1.4.41.tar.gz", hash = "sha256:0292f70d1797e3c54e862e6f30ae474014648bc9c723e14a2fda730adb0a9791"}, 774 | ] 775 | 776 | [package.dependencies] 777 | greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""} 778 | 779 | [package.extras] 780 | aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] 781 | aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] 782 | asyncio = ["greenlet (!=0.4.17)"] 783 | asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] 784 | mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] 785 | mssql = ["pyodbc"] 786 | mssql-pymssql = ["pymssql"] 787 | mssql-pyodbc = ["pyodbc"] 788 | mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] 789 | mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] 790 | mysql-connector = ["mysql-connector-python"] 791 | oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] 792 | postgresql = ["psycopg2 (>=2.7)"] 793 | postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] 794 | postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] 795 | postgresql-psycopg2binary = ["psycopg2-binary"] 796 | postgresql-psycopg2cffi = ["psycopg2cffi"] 797 | pymysql = ["pymysql", "pymysql (<1)"] 798 | sqlcipher = ["sqlcipher3-binary"] 799 | 800 | [[package]] 801 | name = "sqlalchemy2-stubs" 802 | version = "0.0.2a34" 803 | description = "Typing Stubs for SQLAlchemy 1.4" 804 | category = "main" 805 | optional = false 806 | python-versions = ">=3.6" 807 | files = [ 808 | {file = "sqlalchemy2-stubs-0.0.2a34.tar.gz", hash = "sha256:2432137ab2fde1a608df4544f6712427b0b7ff25990cfbbc5a9d1db6c8c6f489"}, 809 | {file = "sqlalchemy2_stubs-0.0.2a34-py3-none-any.whl", hash = "sha256:a313220ac793404349899faf1272e821a62dbe1d3a029bd444faa8d3e966cd07"}, 810 | ] 811 | 812 | [package.dependencies] 813 | typing-extensions = ">=3.7.4" 814 | 815 | [[package]] 816 | name = "sqlmodel" 817 | version = "0.0.8" 818 | description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." 819 | category = "main" 820 | optional = false 821 | python-versions = ">=3.6.1,<4.0.0" 822 | files = [ 823 | {file = "sqlmodel-0.0.8-py3-none-any.whl", hash = "sha256:0fd805719e0c5d4f22be32eb3ffc856eca3f7f20e8c7aa3e117ad91684b518ee"}, 824 | {file = "sqlmodel-0.0.8.tar.gz", hash = "sha256:3371b4d1ad59d2ffd0c530582c2140b6c06b090b32af9b9c6412986d7b117036"}, 825 | ] 826 | 827 | [package.dependencies] 828 | pydantic = ">=1.8.2,<2.0.0" 829 | SQLAlchemy = ">=1.4.17,<=1.4.41" 830 | sqlalchemy2-stubs = "*" 831 | 832 | [[package]] 833 | name = "starlette" 834 | version = "0.26.1" 835 | description = "The little ASGI library that shines." 836 | category = "main" 837 | optional = false 838 | python-versions = ">=3.7" 839 | files = [ 840 | {file = "starlette-0.26.1-py3-none-any.whl", hash = "sha256:e87fce5d7cbdde34b76f0ac69013fd9d190d581d80681493016666e6f96c6d5e"}, 841 | {file = "starlette-0.26.1.tar.gz", hash = "sha256:41da799057ea8620e4667a3e69a5b1923ebd32b1819c8fa75634bbe8d8bea9bd"}, 842 | ] 843 | 844 | [package.dependencies] 845 | anyio = ">=3.4.0,<5" 846 | 847 | [package.extras] 848 | full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] 849 | 850 | [[package]] 851 | name = "typing-extensions" 852 | version = "4.5.0" 853 | description = "Backported and Experimental Type Hints for Python 3.7+" 854 | category = "main" 855 | optional = false 856 | python-versions = ">=3.7" 857 | files = [ 858 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, 859 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, 860 | ] 861 | 862 | [[package]] 863 | name = "uvicorn" 864 | version = "0.22.0" 865 | description = "The lightning-fast ASGI server." 866 | category = "main" 867 | optional = false 868 | python-versions = ">=3.7" 869 | files = [ 870 | {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, 871 | {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, 872 | ] 873 | 874 | [package.dependencies] 875 | click = ">=7.0" 876 | colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} 877 | h11 = ">=0.8" 878 | httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} 879 | python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} 880 | pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} 881 | uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} 882 | watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} 883 | websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} 884 | 885 | [package.extras] 886 | standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] 887 | 888 | [[package]] 889 | name = "uvloop" 890 | version = "0.17.0" 891 | description = "Fast implementation of asyncio event loop on top of libuv" 892 | category = "main" 893 | optional = false 894 | python-versions = ">=3.7" 895 | files = [ 896 | {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, 897 | {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, 898 | {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, 899 | {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, 900 | {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, 901 | {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, 902 | {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, 903 | {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, 904 | {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, 905 | {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, 906 | {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, 907 | {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, 908 | {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, 909 | {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, 910 | {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, 911 | {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, 912 | {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, 913 | {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, 914 | {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, 915 | {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, 916 | {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, 917 | {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, 918 | {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, 919 | {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, 920 | {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, 921 | {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, 922 | {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, 923 | {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, 924 | {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, 925 | {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, 926 | ] 927 | 928 | [package.extras] 929 | dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] 930 | docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] 931 | test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] 932 | 933 | [[package]] 934 | name = "watchfiles" 935 | version = "0.19.0" 936 | description = "Simple, modern and high performance file watching and code reload in python." 937 | category = "main" 938 | optional = false 939 | python-versions = ">=3.7" 940 | files = [ 941 | {file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"}, 942 | {file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"}, 943 | {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"}, 944 | {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"}, 945 | {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"}, 946 | {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"}, 947 | {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"}, 948 | {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"}, 949 | {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"}, 950 | {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"}, 951 | {file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"}, 952 | {file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"}, 953 | {file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"}, 954 | {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"}, 955 | {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"}, 956 | {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"}, 957 | {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"}, 958 | {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"}, 959 | {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"}, 960 | {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"}, 961 | {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"}, 962 | {file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"}, 963 | ] 964 | 965 | [package.dependencies] 966 | anyio = ">=3.0.0" 967 | 968 | [[package]] 969 | name = "websockets" 970 | version = "11.0.3" 971 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 972 | category = "main" 973 | optional = false 974 | python-versions = ">=3.7" 975 | files = [ 976 | {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, 977 | {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, 978 | {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, 979 | {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, 980 | {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, 981 | {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, 982 | {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, 983 | {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, 984 | {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, 985 | {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, 986 | {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, 987 | {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, 988 | {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, 989 | {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, 990 | {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, 991 | {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, 992 | {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, 993 | {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, 994 | {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, 995 | {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, 996 | {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, 997 | {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, 998 | {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, 999 | {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, 1000 | {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, 1001 | {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, 1002 | {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, 1003 | {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, 1004 | {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, 1005 | {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, 1006 | {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, 1007 | {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, 1008 | {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, 1009 | {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, 1010 | {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, 1011 | {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, 1012 | {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, 1013 | {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, 1014 | {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, 1015 | {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, 1016 | {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, 1017 | {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, 1018 | {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, 1019 | {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, 1020 | {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, 1021 | {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, 1022 | {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, 1023 | {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, 1024 | {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, 1025 | {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, 1026 | {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, 1027 | {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, 1028 | {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, 1029 | {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, 1030 | {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, 1031 | {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, 1032 | {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, 1033 | {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, 1034 | {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, 1035 | {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, 1036 | {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, 1037 | {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, 1038 | {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, 1039 | {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, 1040 | {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, 1041 | {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, 1042 | {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, 1043 | {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, 1044 | {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, 1045 | {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, 1046 | ] 1047 | 1048 | [metadata] 1049 | lock-version = "2.0" 1050 | python-versions = "^3.11" 1051 | content-hash = "4b917b77a21a7e8e0c022d01eb008826d74a429b2c1a13b1be577a5c9bd0fe32" 1052 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "streamfinity-fastapi" 3 | version = "0.1.0" 4 | description = "Streamfinity's prototype REST API" 5 | authors = ["Patrick Kalkman "] 6 | readme = "README.md" 7 | packages = [{include = "streamfinity_fastapi"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | fastapi = "^0.95.1" 12 | sqlmodel = "^0.0.8" 13 | python-decouple = "^3.8" 14 | uvicorn = {extras = ["standard"], version = "^0.22.0"} 15 | passlib = "^1.7.4" 16 | python-jose = "^3.3.0" 17 | python-multipart = "^0.0.6" 18 | bcrypt = "^4.0.1" 19 | httpx = "^0.24.0" 20 | pytest = "^7.3.1" 21 | flake8 = "^6.0.0" 22 | gunicorn = "^20.1.0" 23 | 24 | [build-system] 25 | requires = ["poetry-core"] 26 | build-backend = "poetry.core.masonry.api" 27 | 28 | [tool.black] 29 | line-length = 90 30 | 31 | [tool.pyright] 32 | useLibraryCodeForTypes = false 33 | typeCheckingMode = 'strict' 34 | stubPath = './typings' 35 | exclude = [ 36 | '**/__init__.py', 37 | './typings', 38 | ] 39 | strictListInference = true 40 | strictDictionaryInference = true 41 | strictSetInference = true 42 | strictParameterNoneValue = true 43 | reportUntypedFunctionDecorator = "error" 44 | reportUntypedClassDecorator = "error" 45 | reportUntypedBaseClass = "error" 46 | reportUnusedClass = "error" 47 | reportUnnecessaryCast = "error" 48 | reportUnnecessaryComparison = "error" 49 | reportUnnecessaryContains = "error" 50 | reportUnnecessaryIsInstance = "error" 51 | reportImportCycles = "error" 52 | reportDuplicateImport = "error" 53 | 54 | [tool.isort] 55 | line_length = 90 56 | atomic = true 57 | profile = "black" 58 | skip_gitignore = true 59 | 60 | [tool.bandit] 61 | exclude_dirs = ["tests"] 62 | tests = [] 63 | skips = [] -------------------------------------------------------------------------------- /streamfinity_fastapi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/streamfinity_fastapi/__init__.py -------------------------------------------------------------------------------- /streamfinity_fastapi/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any, Generator 3 | 4 | from sqlmodel import Session, create_engine 5 | 6 | is_testing = os.environ.get("TESTING") 7 | 8 | if is_testing: 9 | database_url = "sqlite:///./tests/db/streamfinity_test.db" 10 | else: 11 | database_url = "sqlite:///./streamfinity_fastapi/db/streamfinity.db" 12 | 13 | 14 | engine = create_engine( 15 | database_url, 16 | echo=True, 17 | connect_args={"check_same_thread": False}) 18 | 19 | 20 | def get_session() -> Generator[Session, Any, None]: 21 | with Session(engine) as session: 22 | yield session 23 | -------------------------------------------------------------------------------- /streamfinity_fastapi/db/streamfinity.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/streamfinity_fastapi/db/streamfinity.db -------------------------------------------------------------------------------- /streamfinity_fastapi/routers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/streamfinity_fastapi/routers/__init__.py -------------------------------------------------------------------------------- /streamfinity_fastapi/routers/actors.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from fastapi import APIRouter, Depends, HTTPException, Query, status 4 | from sqlmodel import Session, select 5 | 6 | from streamfinity_fastapi.db import get_session # type: ignore 7 | from streamfinity_fastapi.schemas.movie_actor_schema import Actor, ActorInput 8 | from streamfinity_fastapi.schemas.user_schema import User 9 | from streamfinity_fastapi.security.hashing import get_current_user 10 | 11 | router = APIRouter(prefix="/api/actors") 12 | 13 | 14 | @router.get("/{actor_id}") 15 | def get_actor(actor_id: int, 16 | session: Session = Depends(get_session)) -> Actor: 17 | actor: Actor | None = session.get(Actor, actor_id) 18 | if actor: 19 | return actor 20 | 21 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 22 | detail=f"Actor with id={actor_id} not found") 23 | 24 | 25 | @router.get("/") 26 | def get_actors(name: str | None = Query(None), 27 | birthdate: date | None = Query(None), 28 | nationality: str | None = Query(None), 29 | session: Session = Depends(get_session)) -> list[Actor]: 30 | 31 | query = select(Actor) 32 | 33 | if name: 34 | query = query.where(Actor.last_name == name) 35 | if birthdate: 36 | query = query.where(Actor.date_of_birth == birthdate) 37 | if nationality: 38 | query = query.where(Actor.nationality == nationality) 39 | 40 | return session.exec(query).all() 41 | 42 | 43 | @router.post("/", response_model=Actor, status_code=201) 44 | def add_actor(actor_input: ActorInput, 45 | current_user: User = Depends(get_current_user), 46 | session: Session = Depends(get_session)) -> Actor: 47 | new_actor: Actor = Actor.from_orm(actor_input) 48 | session.add(new_actor) 49 | session.commit() 50 | session.refresh(new_actor) 51 | return new_actor 52 | 53 | 54 | @router.delete("/{actor_id}", status_code=204) 55 | def delete_actor(actor_id: int, 56 | session: Session = Depends(get_session)) -> None: 57 | actor: Actor | None = session.get(Actor, actor_id) 58 | if actor: 59 | session.delete(actor) 60 | session.commit() 61 | else: 62 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 63 | detail=f"Actor with id={actor_id} not found") 64 | 65 | 66 | @router.put("/{actor_id}", response_model=Actor) 67 | def update_actor(actor_id: int, new_actor: ActorInput, 68 | session: Session = Depends(get_session)) -> Actor: 69 | actor: Actor | None = session.get(Actor, actor_id) 70 | if actor: 71 | for field, value in new_actor.dict().items(): 72 | if value is not None: 73 | setattr(actor, field, value) 74 | session.commit() 75 | return actor 76 | else: 77 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 78 | detail=f"Actor with id={actor_id} not found") 79 | -------------------------------------------------------------------------------- /streamfinity_fastapi/routers/movies.py: -------------------------------------------------------------------------------- 1 | from streamfinity_fastapi.db import get_session 2 | from fastapi import APIRouter, Depends, HTTPException, status 3 | from streamfinity_fastapi.schemas.movie_actor_schema import Actor, Movie 4 | from streamfinity_fastapi.schemas.movie_actor_schema import MovieActorLink, MovieInput 5 | from sqlmodel import Session, select 6 | 7 | router = APIRouter(prefix="/api/movies") 8 | 9 | 10 | @router.get("/") 11 | def get_movies(release_year: int | None = None, 12 | session: Session = Depends(get_session)) -> list[Movie]: 13 | query = select(Movie) 14 | if release_year: 15 | query = query.where(Movie.release_year == release_year) 16 | return session.exec(query).all() 17 | 18 | 19 | @router.get("/{movie_id}") 20 | def get_movie(movie_id: int, 21 | session: Session = Depends(get_session)) -> Movie: 22 | movie: Movie | None = session.get(Movie, movie_id) 23 | if movie: 24 | return movie 25 | 26 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 27 | detail=f"Movie with id={movie_id} not found") 28 | 29 | 30 | @router.post("/", response_model=Movie, status_code=201) 31 | def add_movie(movie_input: MovieInput, 32 | session: Session = Depends(get_session)) -> Movie: 33 | new_movie: Movie = Movie.from_orm(movie_input) 34 | session.add(new_movie) 35 | session.commit() 36 | session.refresh(new_movie) 37 | return new_movie 38 | 39 | 40 | @router.delete("/{movie_id}", status_code=204) 41 | def delete_movie(movie_id: int, 42 | session: Session = Depends(get_session)) -> None: 43 | movie: Movie | None = session.get(Movie, movie_id) 44 | if movie: 45 | session.delete(movie) 46 | session.commit() 47 | else: 48 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 49 | detail=f"Movie with id={movie_id} not found") 50 | 51 | 52 | @router.put("/{movie_id}", response_model=Movie) 53 | def update_movie(movie_id: int, new_movie: MovieInput, 54 | session: Session = Depends(get_session)) -> Movie: 55 | movie: Movie | None = session.get(Movie, movie_id) 56 | if movie: 57 | # update movie from database with values from new_movie 58 | for field, value in new_movie.dict().items(): 59 | if value is not None: 60 | setattr(movie, field, value) 61 | session.commit() 62 | return movie 63 | else: 64 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 65 | detail=f"Movie with id={movie_id} not found") 66 | 67 | 68 | @router.post("/{movie_id}/actors/{actor_id}", status_code=204) 69 | def add_actor_to_movie(movie_id: int, actor_id: int, 70 | session: Session = Depends(get_session)) -> None: 71 | movie: Movie | None = session.get(Movie, movie_id) 72 | if not movie: 73 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 74 | detail=f"Movie with id={movie_id} not found") 75 | 76 | actor: Actor | None = session.get(Actor, actor_id) 77 | if not actor: 78 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 79 | detail=f"Actor with id={actor_id} not found") 80 | 81 | link = MovieActorLink(movie_id=movie_id, actor_id=actor_id) 82 | session.add(link) 83 | session.commit() 84 | 85 | 86 | @router.delete("/{movie_id}/actors/{actor_id}", status_code=204) 87 | def remove_actor_from_movie(movie_id: int, actor_id: int, 88 | session: Session = Depends(get_session)) -> None: 89 | movie: Movie | None = session.get(Movie, movie_id) 90 | if not movie: 91 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 92 | detail=f"Movie with id={movie_id} not found") 93 | 94 | actor: Actor | None = session.get(Actor, actor_id) 95 | if not actor: 96 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 97 | detail=f"Actor with id={actor_id} not found") 98 | 99 | link: MovieActorLink | None = session.get(MovieActorLink, (movie_id, actor_id)) 100 | if not link: 101 | raise HTTPException( 102 | status_code=404, 103 | detail=f"Actor {actor_id} not associated with movie {movie_id}") 104 | 105 | session.delete(link) 106 | session.commit() 107 | -------------------------------------------------------------------------------- /streamfinity_fastapi/routers/subscriptions.py: -------------------------------------------------------------------------------- 1 | from streamfinity_fastapi.db import get_session 2 | from fastapi import APIRouter, Depends, HTTPException, Query, status 3 | from streamfinity_fastapi.schemas.subscription_schema import Subscription 4 | from streamfinity_fastapi.schemas.subscription_schema import SubscriptionInput 5 | from sqlmodel import Session, select 6 | 7 | router = APIRouter(prefix="/api/subscriptions") 8 | 9 | 10 | @router.get("/{subscription_id}") 11 | def get_subscription(subscription_id: int, 12 | session: Session = Depends(get_session)) -> Subscription: 13 | subscription: Subscription | None = session.get(Subscription, subscription_id) 14 | if subscription: 15 | return subscription 16 | 17 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 18 | detail=f"Subscription with id={subscription_id} not found") 19 | 20 | 21 | @router.get("/") 22 | def get_subscriptions(plan: str | None = Query(None), 23 | is_active: bool | None = Query(None), 24 | session: Session = Depends(get_session)) -> list[Subscription]: 25 | query = select(Subscription) 26 | 27 | if plan: 28 | query = query.where(Subscription.plan == plan) 29 | if is_active is not None: 30 | query = query.where(Subscription.is_active == is_active) 31 | 32 | return session.exec(query).all() 33 | 34 | 35 | @router.post("/", response_model=Subscription, status_code=201) 36 | def add_subscription(subscription_input: SubscriptionInput, 37 | session: Session = Depends(get_session)) -> Subscription: 38 | new_subscription: Subscription = Subscription.from_orm(subscription_input) 39 | session.add(new_subscription) 40 | session.commit() 41 | session.refresh(new_subscription) 42 | return new_subscription 43 | 44 | 45 | @router.delete("/{subscription_id}", status_code=204) 46 | def delete_subscription(subscription_id: int, 47 | session: Session = Depends(get_session)) -> None: 48 | subscription: Subscription | None = session.get(Subscription, subscription_id) 49 | if subscription: 50 | session.delete(subscription) 51 | session.commit() 52 | else: 53 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 54 | detail=f"Subscription with id={subscription_id} not found") 55 | 56 | 57 | @router.put("/{subscription_id}", response_model=Subscription) 58 | def update_subscription(subscription_id: int, new_subscription: SubscriptionInput, 59 | session: Session = Depends(get_session)) -> Subscription: 60 | subscription: Subscription | None = session.get(Subscription, subscription_id) 61 | if subscription: 62 | for field, value in new_subscription.dict().items(): 63 | if value is not None: 64 | setattr(subscription, field, value) 65 | session.commit() 66 | return subscription 67 | else: 68 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 69 | detail=f"Subscription with id={subscription_id} not found") 70 | -------------------------------------------------------------------------------- /streamfinity_fastapi/routers/token.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | from typing import Annotated 3 | 4 | from streamfinity_fastapi.db import get_session 5 | from fastapi import APIRouter, Depends, HTTPException, status 6 | from fastapi.security import OAuth2PasswordRequestForm 7 | from streamfinity_fastapi.schemas.token_schema import Token 8 | from streamfinity_fastapi.schemas.user_schema import User 9 | from streamfinity_fastapi.security.hashing import create_access_token, verify_password 10 | from sqlmodel import Session, select 11 | 12 | ACCESS_TOKEN_EXPIRE_MINUTES = 3000000 13 | 14 | router = APIRouter(prefix="/api") 15 | 16 | 17 | @router.post("/token", response_model=Token) 18 | def login_for_access_token( 19 | form_data: Annotated[OAuth2PasswordRequestForm, Depends()], 20 | session: Session = Depends(get_session) 21 | ): 22 | query = select(User).where(User.email == form_data.username) 23 | user: User | None = session.exec(query).first() 24 | 25 | if user is None: 26 | raise_401_exception() 27 | 28 | assert user is not None 29 | 30 | if not verify_password(form_data.password, user.password): 31 | raise_401_exception() 32 | 33 | access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 34 | access_token = create_access_token(data={"sub": user.email}, 35 | expires_delta=access_token_expires) 36 | 37 | return {"access_token": access_token, "token_type": "bearer"} 38 | 39 | 40 | def raise_401_exception(): 41 | raise HTTPException( 42 | status_code=status.HTTP_401_UNAUTHORIZED, 43 | detail="Incorrect username or password", 44 | headers={"WWW-Authenticate": "Bearer"}, 45 | ) 46 | -------------------------------------------------------------------------------- /streamfinity_fastapi/routers/users.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, HTTPException, Query, status 2 | from sqlmodel import Session, select 3 | 4 | from streamfinity_fastapi.db import get_session 5 | from streamfinity_fastapi.schemas.user_schema import User, UserInput 6 | from streamfinity_fastapi.security.hashing import get_password_hash 7 | 8 | router = APIRouter(prefix="/api/users") 9 | 10 | 11 | @router.get("/{user_id}") 12 | def get_user(user_id: int, session: Session = Depends(get_session)) -> User: 13 | user: User | None = session.get(User, user_id) 14 | if user: 15 | return user 16 | 17 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 18 | detail=f"User with id={user_id} not found") 19 | 20 | 21 | @router.get("/") 22 | def get_users(email: str | None = Query(None), 23 | session: Session = Depends(get_session)) -> list[User]: 24 | query = select(User) 25 | 26 | if email: 27 | query = query.where(User.email == email) 28 | 29 | return session.exec(query).all() 30 | 31 | 32 | @router.post("/", response_model=User, status_code=201) 33 | def add_user(user_input: UserInput, session: Session = Depends(get_session)) -> User: 34 | hashed_password = get_password_hash(user_input.password) 35 | user_input.password = hashed_password 36 | 37 | new_user: User = User.from_orm(user_input) 38 | session.add(new_user) 39 | session.commit() 40 | session.refresh(new_user) 41 | return new_user 42 | 43 | 44 | @router.delete("/{user_id}", status_code=204) 45 | def delete_user(user_id: int, session: Session = Depends(get_session)) -> None: 46 | user: User | None = session.get(User, user_id) 47 | if user: 48 | session.delete(user) 49 | session.commit() 50 | else: 51 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 52 | detail=f"User with id={user_id} not found") 53 | 54 | 55 | @router.put("/{user_id}", response_model=User) 56 | def update_user(user_id: int, new_user: UserInput, 57 | session: Session = Depends(get_session)) -> User: 58 | user: User | None = session.get(User, user_id) 59 | if user: 60 | for field, value in new_user.dict().items(): 61 | if value is not None: 62 | setattr(user, field, value) 63 | session.commit() 64 | return user 65 | else: 66 | raise HTTPException(status_code=404, detail=f"User with id={user_id} not found") 67 | -------------------------------------------------------------------------------- /streamfinity_fastapi/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/streamfinity_fastapi/schemas/__init__.py -------------------------------------------------------------------------------- /streamfinity_fastapi/schemas/movie_actor_schema.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from sqlmodel import Field, Relationship, SQLModel 4 | 5 | 6 | class MovieInput(SQLModel): 7 | title: str 8 | length: int 9 | synopsis: str 10 | release_year: int 11 | director: str 12 | genre: str 13 | rating: int | None = None 14 | 15 | 16 | class MovieActorLink(SQLModel, table=True): 17 | movie_id: int = Field(foreign_key="movie.id", 18 | primary_key=True, default=None) 19 | actor_id: int = Field(foreign_key="actor.id", 20 | primary_key=True, default=None) 21 | 22 | 23 | class Movie(MovieInput, table=True): 24 | id: int | None = Field(primary_key=True, default=None) 25 | actors: list["Actor"] = Relationship(back_populates="movies", 26 | link_model=MovieActorLink) 27 | 28 | 29 | class MovieOutput(MovieInput): 30 | id: int 31 | actors: list["Actor"] = [] 32 | 33 | 34 | class ActorInput(SQLModel): 35 | first_name: str 36 | last_name: str 37 | date_of_birth: date 38 | nationality: str 39 | biography: str | None = None 40 | profile_picture_url: str | None = None 41 | 42 | 43 | class Actor(ActorInput, table=True): 44 | id: int | None = Field(primary_key=True, default=None) 45 | movies: list[Movie] = Relationship(back_populates="actors", 46 | link_model=MovieActorLink) 47 | -------------------------------------------------------------------------------- /streamfinity_fastapi/schemas/subscription_schema.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from sqlmodel import Field, SQLModel 4 | 5 | 6 | class SubscriptionInput(SQLModel): 7 | user_id: int 8 | plan: str 9 | start_date: date 10 | end_date: date | None = None 11 | is_active: bool = True 12 | 13 | 14 | class Subscription(SubscriptionInput, table=True): 15 | id: int = Field(default=None, primary_key=True) 16 | -------------------------------------------------------------------------------- /streamfinity_fastapi/schemas/token_schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Token(BaseModel): 5 | access_token: str 6 | token_type: str 7 | -------------------------------------------------------------------------------- /streamfinity_fastapi/schemas/user_schema.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import Field, SQLModel 2 | 3 | 4 | class UserInput(SQLModel): 5 | email: str 6 | first_name: str 7 | last_name: str 8 | password: str 9 | is_active: bool = True 10 | 11 | 12 | class User(UserInput, table=True): 13 | id: int = Field(default=None, primary_key=True) 14 | -------------------------------------------------------------------------------- /streamfinity_fastapi/security/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/streamfinity_fastapi/security/__init__.py -------------------------------------------------------------------------------- /streamfinity_fastapi/security/hashing.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime, timedelta 3 | from typing import Dict 4 | 5 | from streamfinity_fastapi.db import get_session 6 | from decouple import config 7 | from fastapi import Depends, HTTPException, status 8 | from fastapi.security import OAuth2PasswordBearer 9 | from jose import JWTError, jwt 10 | from passlib.context import CryptContext 11 | from streamfinity_fastapi.schemas.user_schema import User 12 | from sqlmodel import Session, select 13 | 14 | SECRET_KEY: str = config('SECRET_KEY', cast=str, default='secret') 15 | ALGORITHM: str = config('ALGORITHM', cast=str, default='HS256') 16 | 17 | 18 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 19 | 20 | 21 | def get_password_hash(password: str): 22 | return pwd_context.hash(password) 23 | 24 | 25 | def verify_password(plain_password: str, hashed_password: str): 26 | return pwd_context.verify(plain_password, hashed_password) 27 | 28 | 29 | def create_access_token(data: Dict[str, str | datetime], 30 | expires_delta: timedelta | None = None): 31 | to_encode = data.copy() 32 | if expires_delta: 33 | expire = datetime.utcnow() + expires_delta 34 | else: 35 | expire = datetime.utcnow() + timedelta(minutes=15) 36 | to_encode.update({"exp": expire}) 37 | encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) 38 | return encoded_jwt 39 | 40 | 41 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") 42 | 43 | 44 | def get_current_user(token: str = Depends(oauth2_scheme), 45 | session: Session = Depends(get_session)) -> User: 46 | try: 47 | payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 48 | username: str | None = payload.get("sub") 49 | if username is None: 50 | raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, 51 | detail="Could not validate credentials") 52 | except JWTError: 53 | raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, 54 | detail="Could not validate credentials") 55 | 56 | query = select(User).where(User.email == username) 57 | user: User | None = session.exec(query).first() 58 | if user is None: 59 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, 60 | detail="User not found") 61 | 62 | return user 63 | -------------------------------------------------------------------------------- /streamfinity_fastapi/streamfinity.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from fastapi import FastAPI 3 | from streamfinity_fastapi.routers import actors, movies, subscriptions, token, users 4 | from sqlmodel import SQLModel 5 | from streamfinity_fastapi.db import engine 6 | 7 | app = FastAPI(title="Streamfinity API", version="0.1.0") 8 | app.include_router(movies.router) 9 | app.include_router(actors.router) 10 | app.include_router(subscriptions.router) 11 | app.include_router(users.router) 12 | app.include_router(token.router) 13 | 14 | 15 | @app.on_event("startup") 16 | def on_startup() -> None: 17 | SQLModel.metadata.create_all(engine) 18 | 19 | 20 | if __name__ == "__main__": 21 | uvicorn.run(app, reload=True) 22 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from sqlmodel import SQLModel 4 | 5 | os.environ["TESTING"] = "1" 6 | 7 | from streamfinity_fastapi.db import engine 8 | 9 | 10 | @pytest.fixture(scope="session", autouse=True) 11 | def create_test_database(): 12 | # Create the test database tables 13 | SQLModel.metadata.create_all(engine) 14 | 15 | # Run the tests 16 | yield 17 | 18 | # Clean up (e.g., drop the test database tables) 19 | SQLModel.metadata.drop_all(engine) 20 | -------------------------------------------------------------------------------- /tests/db/streamfinity_test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/streamfinity-fastapi/f479c360b735bcadb4a1d27cac3f8da71e171f21/tests/db/streamfinity_test.db -------------------------------------------------------------------------------- /tests/test_actors_api.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from streamfinity_fastapi.schemas.movie_actor_schema import ActorInput 3 | from streamfinity_fastapi.schemas.user_schema import UserInput 4 | from streamfinity_fastapi.streamfinity import app 5 | from fastapi import status 6 | from fastapi.testclient import TestClient 7 | 8 | client = TestClient(app) 9 | 10 | # Test data 11 | test_actor = ActorInput( 12 | first_name="Test", 13 | last_name="Actor", 14 | date_of_birth=date(1990, 1, 1), 15 | nationality="American", 16 | biography="Test biography", 17 | profile_picture_url="https://example.com/test_actor.jpg", 18 | ) 19 | 20 | # A valid JWT token for testing generated by using a dummy secret key 21 | access_token = ( 22 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." 23 | "eyJzdWIiOiJwa2Fsa2llMkBnbWFpbC5jb20iLCJleHAiOjE4NjM1NjkzMDZ9." 24 | "7_92eJtzh3v0VNBoUJZknyoQI1zxSWIEy_AG12RGmuc" 25 | ) 26 | 27 | test_user = UserInput( 28 | email="pkalkie2@gmail.com", 29 | password="test_password", 30 | first_name="Test", 31 | last_name="User", 32 | is_active=True 33 | ) 34 | 35 | def create_user() -> dict: 36 | response = client.post("/api/users/", json=test_user.dict()) 37 | return response.json() 38 | 39 | 40 | def get_test_actor_json(): 41 | json = { 42 | **test_actor.dict(), 43 | "date_of_birth": test_actor.date_of_birth.isoformat(), 44 | } 45 | return json 46 | 47 | 48 | def create_actor(): 49 | create_user() 50 | json = get_test_actor_json() 51 | response = client.post("/api/actors/", json=json, 52 | headers={"Authorization": f"Bearer {access_token}"}) 53 | assert response.status_code == status.HTTP_201_CREATED 54 | return response.json()["id"] 55 | 56 | 57 | def test_create_actor(): 58 | create_user() 59 | json = get_test_actor_json() 60 | response = client.post("/api/actors/", json=json, 61 | headers={"Authorization": f"Bearer {access_token}"}) 62 | assert response.status_code == status.HTTP_201_CREATED 63 | assert response.json()["first_name"] == test_actor.first_name 64 | assert response.json()["last_name"] == test_actor.last_name 65 | 66 | 67 | def test_get_actor(): 68 | actor_id = create_actor() 69 | response = client.get(f"/api/actors/{actor_id}") 70 | assert response.status_code == status.HTTP_200_OK 71 | assert response.json()["first_name"] == test_actor.first_name 72 | assert response.json()["last_name"] == test_actor.last_name 73 | 74 | 75 | def test_get_actors(): 76 | response = client.get("/api/actors/") 77 | assert response.status_code == status.HTTP_200_OK 78 | assert isinstance(response.json(), list) 79 | 80 | 81 | def test_update_actor(): 82 | actor_id = create_actor() 83 | updated_actor = test_actor.copy( 84 | update={"nationality": "British"} 85 | ).dict() 86 | updated_actor["date_of_birth"] = updated_actor["date_of_birth"].isoformat() 87 | response = client.put( 88 | f"/api/actors/{actor_id}", json=updated_actor 89 | ) 90 | assert response.status_code == status.HTTP_200_OK 91 | assert response.json()["nationality"] == "British" 92 | 93 | 94 | def test_delete_actor(): 95 | actor_id = create_actor() 96 | response = client.delete(f"/api/actors/{actor_id}") 97 | assert response.status_code == status.HTTP_204_NO_CONTENT 98 | -------------------------------------------------------------------------------- /tests/test_movies_api.py: -------------------------------------------------------------------------------- 1 | from streamfinity_fastapi.schemas.movie_actor_schema import MovieInput 2 | from streamfinity_fastapi.streamfinity import app 3 | from fastapi import status 4 | from fastapi.testclient import TestClient 5 | 6 | client = TestClient(app) 7 | 8 | # Test data 9 | test_movie = MovieInput( 10 | title="Test Movie", 11 | length=120, 12 | synopsis="A test movie", 13 | release_year=2022, 14 | director="John Doe", 15 | genre="Action", 16 | rating=8, 17 | ) 18 | 19 | 20 | def create_movie(): 21 | response = client.post("/api/movies/", json=test_movie.dict()) 22 | assert response.status_code == status.HTTP_201_CREATED 23 | return response.json()["id"] 24 | 25 | 26 | def test_create_movie(): 27 | response = client.post("/api/movies/", json=test_movie.dict()) 28 | assert response.status_code == status.HTTP_201_CREATED 29 | assert response.json()["title"] == test_movie.title 30 | assert response.json()["director"] == test_movie.director 31 | 32 | 33 | def test_get_movie(): 34 | movie_id = create_movie() 35 | response = client.get(f"/api/movies/{movie_id}") 36 | assert response.status_code == status.HTTP_200_OK 37 | assert response.json()["title"] == test_movie.title 38 | assert response.json()["director"] == test_movie.director 39 | 40 | 41 | def test_get_movies(): 42 | response = client.get("/api/movies/") 43 | assert response.status_code == status.HTTP_200_OK 44 | assert isinstance(response.json(), list) 45 | 46 | 47 | def test_update_movie(): 48 | movie_id = create_movie() 49 | updated_movie = test_movie.copy(update={"title": "Updated Test Movie"}).dict() 50 | response = client.put(f"/api/movies/{movie_id}", json=updated_movie) 51 | assert response.status_code == status.HTTP_200_OK 52 | assert response.json()["title"] == "Updated Test Movie" 53 | 54 | 55 | def test_delete_movie(): 56 | movie_id = create_movie() 57 | response = client.delete(f"/api/movies/{movie_id}") 58 | assert response.status_code == status.HTTP_204_NO_CONTENT 59 | -------------------------------------------------------------------------------- /tests/test_subscriptions.py: -------------------------------------------------------------------------------- 1 | from datetime import date, timedelta 2 | from streamfinity_fastapi.schemas.subscription_schema import SubscriptionInput 3 | from streamfinity_fastapi.streamfinity import app 4 | from fastapi import status 5 | from fastapi.testclient import TestClient 6 | 7 | client = TestClient(app) 8 | 9 | # Test data 10 | test_subscription = SubscriptionInput( 11 | user_id=1, 12 | plan="Basic", 13 | start_date=date.today(), 14 | end_date=date.today() + timedelta(days=30), 15 | is_active=True, 16 | ) 17 | 18 | 19 | def get_test_subscription_json(): 20 | assert test_subscription.end_date is not None 21 | json = { 22 | **test_subscription.dict(), 23 | "start_date": test_subscription.start_date.isoformat(), 24 | "end_date": test_subscription.end_date.isoformat(), 25 | } 26 | return json 27 | 28 | 29 | def create_subscription(): 30 | json = get_test_subscription_json() 31 | response = client.post("/api/subscriptions/", json=json) 32 | assert response.status_code == status.HTTP_201_CREATED 33 | return response.json()["id"] 34 | 35 | 36 | def test_create_subscription(): 37 | json = get_test_subscription_json() 38 | response = client.post("/api/subscriptions/", json=json) 39 | assert response.status_code == status.HTTP_201_CREATED 40 | assert response.json()["user_id"] == test_subscription.user_id 41 | assert response.json()["plan"] == test_subscription.plan 42 | 43 | 44 | def test_get_subscription(): 45 | subscription_id = create_subscription() 46 | response = client.get(f"/api/subscriptions/{subscription_id}") 47 | assert response.status_code == status.HTTP_200_OK 48 | assert response.json()["user_id"] == test_subscription.user_id 49 | assert response.json()["plan"] == test_subscription.plan 50 | 51 | 52 | def test_get_subscriptions(): 53 | response = client.get("/api/subscriptions/") 54 | assert response.status_code == status.HTTP_200_OK 55 | assert isinstance(response.json(), list) 56 | 57 | 58 | def test_update_subscription(): 59 | subscription_id = create_subscription() 60 | updated_subscription = test_subscription.copy( 61 | update={"plan": "Premium"} 62 | ).dict() 63 | updated_subscription["start_date"] = updated_subscription["start_date"].isoformat() 64 | updated_subscription["end_date"] = updated_subscription["end_date"].isoformat() 65 | response = client.put( 66 | f"/api/subscriptions/{subscription_id}", json=updated_subscription 67 | ) 68 | assert response.status_code == status.HTTP_200_OK 69 | assert response.json()["plan"] == "Premium" 70 | 71 | 72 | def test_delete_subscription(): 73 | subscription_id = create_subscription() 74 | response = client.delete(f"/api/subscriptions/{subscription_id}") 75 | assert response.status_code == status.HTTP_204_NO_CONTENT 76 | -------------------------------------------------------------------------------- /tests/test_users_api.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | from streamfinity_fastapi.streamfinity import app 3 | from streamfinity_fastapi.schemas.user_schema import UserInput 4 | 5 | 6 | client = TestClient(app) 7 | 8 | # Test data 9 | test_user = UserInput( 10 | email="pkalkie2@gmail.com", 11 | password="test_password", 12 | first_name="Test", 13 | last_name="User", 14 | is_active=True 15 | ) 16 | 17 | 18 | def create_user() -> dict: 19 | response = client.post("/api/users/", json=test_user.dict()) 20 | return response.json() 21 | 22 | 23 | def test_add_user(): 24 | response = client.post("/api/users/", json=test_user.dict()) 25 | assert response.status_code == 201 26 | data = response.json() 27 | assert data["email"] == test_user.email 28 | 29 | 30 | def test_get_user(): 31 | user = create_user() 32 | user_id = user["id"] 33 | 34 | response = client.get(f"/api/users/{user_id}") 35 | assert response.status_code == 200 36 | assert response.json()["email"] == test_user.email 37 | 38 | 39 | def test_get_users(): 40 | create_user() 41 | 42 | response = client.get("/api/users/") 43 | assert response.status_code == 200 44 | users = response.json() 45 | assert len(users) > 0 46 | assert users[0]["email"] == test_user.email 47 | 48 | 49 | def test_update_user(): 50 | user = create_user() 51 | user_id = user["id"] 52 | 53 | updated_user = test_user.copy(update={"email": "updated@example.com"}) 54 | response = client.put(f"/api/users/{user_id}", json=updated_user.dict()) 55 | assert response.status_code == 200 56 | assert response.json()["email"] == "updated@example.com" 57 | 58 | 59 | def test_delete_user(): 60 | user = create_user() 61 | user_id = user["id"] 62 | 63 | response = client.delete(f"/api/users/{user_id}") 64 | assert response.status_code == 204 65 | 66 | response = client.get(f"/api/users/{user_id}") 67 | assert response.status_code == 404 68 | --------------------------------------------------------------------------------