From ca49a3b31ad5223c4039811bde5e19d0b74585ff Mon Sep 17 00:00:00 2001 From: Alexandre Teles Date: Sun, 9 Oct 2022 16:56:24 -0300 Subject: [PATCH] feat: implement clients, authentication, announcement and changelogs (#11) * feat: resolves #5, resolves #4 (#7) * Implements client generation and management * fix announcements endpoints * change annoucements model * bump deps * sync with main * refactor: adopt some functional standards in Releases.py * feat: add new workflows * chore: remove unused files * refactor: update build badge * refactor: move files around and delete unused ones * feat: add authentication endpoints * refactor: clean up code on Clients.py controller * fix: fix the client secret update endpoint * refactor: clean up authentication code * feat: add authentication to client endpoints * chore: bump deps * feat: add admin user generation * feature: add /changelogs endpoint (#10) --- .gitignore | 3 +- .vscode/settings.json | 3 + Dockerfile | 12 +- README.md | 38 +- config.toml | 42 +- deploy/docker-compose.yml | 15 +- deploy/portainer-stack.yml | 16 +- env.sh | 11 - main.py | 421 ++++++++- modules/models/ResponseModels.py | 41 - mypy.ini | 12 + poetry.lock | 852 ++++++++++++------ pyproject.toml | 24 +- requirements.txt | 47 +- run.sh | 6 +- {modules => src}/__init__.py | 0 src/controllers/Announcements.py | 102 +++ src/controllers/Auth.py | 7 + src/controllers/Clients.py | 351 ++++++++ {modules => src/controllers}/Releases.py | 125 ++- .../models => src/controllers}/__init__.py | 0 src/models/AnnouncementModels.py | 46 + src/models/ClientModels.py | 24 + src/models/GeneralErrors.py | 51 ++ {modules => src}/models/ResponseFields.py | 29 +- src/models/ResponseModels.py | 101 +++ {modules/utils => src/models}/__init__.py | 0 src/utils/Generators.py | 30 + src/utils/HTTPXClient.py | 32 + {modules => src}/utils/InternalCache.py | 60 +- {modules => src}/utils/Logger.py | 37 +- src/utils/RedisConnector.py | 23 + src/utils/__init__.py | 0 33 files changed, 2058 insertions(+), 503 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 env.sh delete mode 100644 modules/models/ResponseModels.py rename {modules => src}/__init__.py (100%) create mode 100644 src/controllers/Announcements.py create mode 100644 src/controllers/Auth.py create mode 100644 src/controllers/Clients.py rename {modules => src/controllers}/Releases.py (55%) rename {modules/models => src/controllers}/__init__.py (100%) create mode 100644 src/models/AnnouncementModels.py create mode 100644 src/models/ClientModels.py create mode 100644 src/models/GeneralErrors.py rename {modules => src}/models/ResponseFields.py (82%) create mode 100644 src/models/ResponseModels.py rename {modules/utils => src/models}/__init__.py (100%) create mode 100644 src/utils/Generators.py create mode 100644 src/utils/HTTPXClient.py rename {modules => src}/utils/InternalCache.py (55%) rename {modules => src}/utils/Logger.py (64%) create mode 100644 src/utils/RedisConnector.py create mode 100644 src/utils/__init__.py diff --git a/.gitignore b/.gitignore index 494802c..fbde709 100644 --- a/.gitignore +++ b/.gitignore @@ -152,4 +152,5 @@ cython_debug/ #.idea/ # PROJECT SPECIFIC -setup_env.sh \ No newline at end of file +setup_env.sh +admin_info.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a6735e5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "off" +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ced361b..7392ede 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,14 @@ FROM python:3.10-slim ARG GITHUB_TOKEN ENV GITHUB_TOKEN $GITHUB_TOKEN -ARG UVICORN_HOST -ENV UVICORN_HOST $UVICORN_HOST +ARG HYPERCORN_HOST +ENV HYPERCORN_HOST $HYPERCORN_HOST -ARG UVICORN_PORT -ENV UVICORN_PORT $UVICORN_PORT +ARG HYPERCORN_PORT +ENV HYPERCORN_PORT $HYPERCORN_PORT -ARG UVICORN_LOG_LEVEL -ENV UVICORN_LOG_LEVEL $UVICORN_LOG_LEVEL +ARG HYPERCORN_LOG_LEVEL +ENV HYPERCORN_LOG_LEVEL $HYPERCORN_LOG_LEVEL WORKDIR /usr/src/app diff --git a/README.md b/README.md index 6d2421f..c8f964d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This is a simple API that returns the latest ReVanced releases, patches and cont ## Usage -The API is available at [https://revanced-releases-api.afterst0rm.xyz/](https://revanced-releases-api.afterst0rm.xyz/). +The API is available at [https://releases.rvcd.win/](https://releases.rvcd.win/). You can deploy your own instance by cloning this repository, editing the `docker-compose.yml` file to include your GitHub token and running `docker-compose up` or `docker-compose up --build` if you want to build the image locally instead of pulling from GHCR. Optionally you can run the application without Docker by running `poetry install` and `poetry run ./run.sh`. In this case, you'll also need a redis server and setup the following environment variables on your system. @@ -28,9 +28,39 @@ If you don't have a Sentry instance, we recommend using [GlitchTip](https://glit ### API Endpoints -* [tools](https://revanced-releases-api.afterst0rm.xyz/tools) - Returns the latest version of all ReVanced tools and Vanced MicroG -* [patches](https://revanced-releases-api.afterst0rm.xyz/patches) - Returns the latest version of all ReVanced patches -* [contributors](https://revanced-releases-api.afterst0rm.xyz/contributors) - Returns contributors for all ReVanced projects +* [tools](https://releases.rvcd.win/tools) - Returns the latest version of all ReVanced tools and Vanced MicroG +* [patches](https://releases.rvcd.win/patches) - Returns the latest version of all ReVanced patches +* [contributors](https://releases.rvcd.win/contributors) - Returns contributors for all ReVanced projects +* [announcement](https://releases.rvcd.win/announcement) - Returns the latest announcement for the ReVanced projects + +## Clients + +The API has no concept of users. It is meant to be used by clients, such as the [ReVanced Manager](https://github.com/revanced/revanced-manager). + +When the API is deployed for the first time it'll create a new client with admin permissions. The credentials can be found at the log console or in the file `admin_info.json` in the root directory of the project. Only admin clients can create, edit and delete other clients. If you're going to use any of the authenticated endpoints, you'll need to create a client and use its credentials. Please follow the API documentation for more information. + +## Authentication + +The API uses [PASETO](https://paseto.io/) tokens for authorization. To authenticate, you need to send a POST request to `/auth` with the following JSON body: + +```json +{ + "id": "your_client_id", + "secret": "your_client_secret" +} +``` + +The API will answer with a PASETO token and a refresh token that you can use to authorize your requests. You can use the token in the `Authorization` header of your requests, like this: + +``` +Authorization: Bearer +``` + +That token will be valid for 24 hours. After that, you'll need to refresh it by sending a POST request to `/auth/refresh` with your `refresh_token` in the `Authorization` header. + +Refresh tokens are valid for 30 days. After that, you'll need to authenticate again and get new tokens. + +Some endpoints might require fresh tokens, forcing you to authenticate. ## Contributing diff --git a/config.toml b/config.toml index a0cd9bf..2d7a5e1 100644 --- a/config.toml +++ b/config.toml @@ -8,42 +8,38 @@ Changelogs are not included but can be found on the [ReVanced Repositories](http The team also have a [Discord Server](https://revanced.app/discord) if you need help. -## API Endpoints - -* [tools](/tools) - Returns the latest version of all ReVanced tools and Vanced MicroG -* [patches](/patches) - Returns the latest version of all ReVanced patches -* [contributors](/contributors) - Returns contributors for all ReVanced projects - -## Additional Information +## Important Information * Rate Limiting - 60 requests per minute * Cache - 5 minutes +* Token duration - 1 hour +* Token refresh - 30 days -## Important Notes +## Additional Notes 1. Although we will try to avoid breaking changes, we can't guarantee that it won't happen. -2. Okay, the api is now cached and rate limited (per endpoint). But please don't abuse it, we don't want to have to block you. -3. Make sure to implement a cache system on your end to avoid unnecessary requests. +2. Make sure to implement a cache system on your end to avoid unnecessary requests. +3. API abuse will result in IP blocks. Godspeed 💀 """ -version = "0.10 beta" +version = "0.8 RC" [license] name = "AGPL-3.0" url = "https://www.gnu.org/licenses/agpl-3.0.en.html" -[uvicorn] - -host = "0.0.0.0" -port = 8000 - [slowapi] limit = "60/minute" +[logging] + +level = "INFO" +json_logs = false + [cache] expire = 120 database = 0 @@ -52,12 +48,16 @@ database = 0 expire = 300 database = 1 +[clients] +database = 2 + +[tokens] +database = 3 + +[announcements] +database = 4 + [app] repositories = ["TeamVanced/VancedMicroG", "revanced/revanced-cli", "revanced/revanced-patcher", "revanced/revanced-patches", "revanced/revanced-integrations", "revanced/revanced-manager"] -[logging] - -level = "INFO" -json_logs = false -redis_database = 2 \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 1aeca5d..c3dde63 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -1,14 +1,13 @@ version: "3.8" -volumes: - redis-data: - driver: local services: redis: container_name: revanced-releases-api-redis - image: redis:latest + image: redis-stack-server:latest + environment: + - REDIS_ARGS=--save 60 1 --appendonly yes volumes: - - redis-data:/data + - /data/redis/revanced-releases-api:/data networks: - infra restart: always @@ -19,9 +18,9 @@ services: - GITHUB_TOKEN=YOUR_GITHUB_TOKEN - REDIS_URL=revanced-releases-api-redis - REDIS_PORT=6379 - - UVICORN_HOST=0.0.0.0 - - UVICORN_PORT=8000 - - UVICORN_LOG_LEVEL=debug + - HYPERCORN_HOST=0.0.0.0 + - HYPERCORN_PORT=8000 + - HYPERCORN_LOG_LEVEL=debug - SENTRY_DSN=YOUR_SENTRY_DSN ports: - 127.0.0.1:7934:8000 diff --git a/deploy/portainer-stack.yml b/deploy/portainer-stack.yml index aa3efe8..482be18 100644 --- a/deploy/portainer-stack.yml +++ b/deploy/portainer-stack.yml @@ -1,16 +1,14 @@ --- version: "3.8" -volumes: - redis-data: - driver: local - services: redis: container_name: revanced-releases-api-redis - image: redis:latest + image: redis-stack-server:latest + environment: + - REDIS_ARGS=--save 60 1 --appendonly yes volumes: - - redis-data:/data + - /data/redis/revanced-releases-api:/data networks: - infra restart: always @@ -21,9 +19,9 @@ services: - GITHUB_TOKEN=YOUR_GITHUB_TOKEN - REDIS_URL=revanced-releases-api-redis - REDIS_PORT=6379 - - UVICORN_HOST=0.0.0.0 - - UVICORN_PORT=8000 - - UVICORN_LOG_LEVEL=debug + - HYPERCORN_HOST=0.0.0.0 + - HYPERCORN_PORT=8000 + - HYPERCORN_LOG_LEVEL=debug - SENTRY_DSN=YOUR_SENTRY_DSN ports: - 127.0.0.1:7934:8000 diff --git a/env.sh b/env.sh deleted file mode 100644 index 568e846..0000000 --- a/env.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# This script is used to setup the environment variables - -export GITHUB_TOKEN=your_token -export UVICORN_HOST=0.0.0.0 -export UVICORN_PORT=8000 -export UVICORN_LOG_LEVEL=debug -export REDIS_URL=127.0.0.1 -export REDIS_PORT=6379 -export SENTRY_DSN=your_sentry_dsn \ No newline at end of file diff --git a/main.py b/main.py index 099d817..21f581b 100755 --- a/main.py +++ b/main.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 +import binascii import os +from typing import Coroutine import toml -import uvicorn -import aioredis import sentry_sdk +import asyncio +import uvloop -from fastapi import FastAPI, Request, Response -from fastapi.responses import RedirectResponse +from fastapi import FastAPI, Request, Response, status, HTTPException, Depends +from fastapi.responses import RedirectResponse, JSONResponse, UJSONResponse from slowapi.util import get_remote_address from slowapi import Limiter, _rate_limit_exceeded_handler @@ -16,15 +18,29 @@ from fastapi_cache import FastAPICache from fastapi_cache.decorator import cache from slowapi.errors import RateLimitExceeded from fastapi_cache.backends.redis import RedisBackend +from fastapi.exceptions import RequestValidationError + +from fastapi_paseto_auth import AuthPASETO +from fastapi_paseto_auth.exceptions import AuthPASETOException from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.httpx import HttpxIntegration from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration -from modules.Releases import Releases -import modules.models.ResponseModels as ResponseModels +import src.controllers.Auth as Auth +from src.controllers.Releases import Releases +from src.controllers.Clients import Clients +from src.controllers.Announcements import Announcements -import modules.utils.Logger as Logger +from src.utils.Generators import Generators +from src.utils.RedisConnector import RedisConnector + +import src.models.ClientModels as ClientModels +import src.models.GeneralErrors as GeneralErrors +import src.models.ResponseModels as ResponseModels +import src.models.AnnouncementModels as AnnouncementModels + +import src.utils.Logger as Logger # Enable sentry logging @@ -40,18 +56,23 @@ sentry_sdk.init(os.environ['SENTRY_DSN'], traces_sample_rate=1.0, integrations=[ config: dict = toml.load("config.toml") -# Redis connection parameters +# Class instances -redis_config: dict[ str, str | int ] = { - "url": f"redis://{os.environ['REDIS_URL']}", - "port": os.environ['REDIS_PORT'], - "database": config['cache']['database'], -} - -# Create releases instance +generators = Generators() releases = Releases() +clients = Clients() + +announcements = Announcements() + +# Setup admin client +uvloop.install() + +loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() +coroutine: Coroutine = clients.setup_admin() +loop.run_until_complete(coroutine) + # Create FastAPI instance app = FastAPI(title=config['docs']['title'], @@ -59,7 +80,9 @@ app = FastAPI(title=config['docs']['title'], version=config['docs']['version'], license_info={"name": config['license']['name'], "url": config['license']['url'] - }) + }, + default_response_class=UJSONResponse + ) # Slowapi limiter @@ -73,9 +96,35 @@ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) async def get_cache() -> int: return 1 +# Setup PASETO + +@AuthPASETO.load_config +def get_config(): + return Auth.PasetoSettings() + +# Setup custom error handlers + +@app.exception_handler(AuthPASETOException) +async def authpaseto_exception_handler(request: Request, exc: AuthPASETOException): + return JSONResponse(status_code=exc.status_code, content={"detail": exc.message}) + +@app.exception_handler(AttributeError) +async def validation_exception_handler(request, exc): + return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={ + "error": "Unprocessable Entity" + }) + +@app.exception_handler(binascii.Error) +async def invalid_token_exception_handler(request, exc): + return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + }) + # Routes -@app.get("/", response_class=RedirectResponse, status_code=301) +@app.get("/", response_class=RedirectResponse, + status_code=status.HTTP_301_MOVED_PERMANENTLY, tags=['Root']) @limiter.limit(config['slowapi']['limit']) async def root(request: Request, response: Response) -> RedirectResponse: """Brings up API documentation @@ -85,7 +134,7 @@ async def root(request: Request, response: Response) -> RedirectResponse: """ return RedirectResponse(url="/docs") -@app.get('/tools', response_model=ResponseModels.ToolsResponseModel) +@app.get('/tools', response_model=ResponseModels.ToolsResponseModel, tags=['ReVanced Tools']) @limiter.limit(config['slowapi']['limit']) @cache(config['cache']['expire']) async def tools(request: Request, response: Response) -> dict: @@ -96,7 +145,7 @@ async def tools(request: Request, response: Response) -> dict: """ return await releases.get_latest_releases(config['app']['repositories']) -@app.get('/patches', response_model=ResponseModels.PatchesResponseModel) +@app.get('/patches', response_model=ResponseModels.PatchesResponseModel, tags=['ReVanced Tools']) @limiter.limit(config['slowapi']['limit']) @cache(config['cache']['expire']) async def patches(request: Request, response: Response) -> dict: @@ -108,7 +157,7 @@ async def patches(request: Request, response: Response) -> dict: return await releases.get_patches_json() -@app.get('/contributors', response_model=ResponseModels.ContributorsResponseModel) +@app.get('/contributors', response_model=ResponseModels.ContributorsResponseModel, tags=['ReVanced Tools']) @limiter.limit(config['slowapi']['limit']) @cache(config['cache']['expire']) async def contributors(request: Request, response: Response) -> dict: @@ -119,28 +168,334 @@ async def contributors(request: Request, response: Response) -> dict: """ return await releases.get_contributors(config['app']['repositories']) -@app.head('/ping', status_code=204) +@app.get('/changelogs/{org}/{repo}', response_model=ResponseModels.ChangelogsResponseModel, tags=['ReVanced Tools']) @limiter.limit(config['slowapi']['limit']) -async def ping(request: Request, response: Response) -> None: - """Check if the API is running. +@cache(config['cache']['expire']) +async def changelogs(request: Request, response: Response, org: str, repo: str, path: str) -> dict: + """Get the latest changes from a repository. Returns: - None + json: list of commits """ - return None + return await releases.get_commits( + org=org, + repository=repo, + path=path + ) + +@app.post('/client', response_model=ClientModels.ClientModel, status_code=status.HTTP_201_CREATED, tags=['Clients']) +@limiter.limit(config['slowapi']['limit']) +async def create_client(request: Request, response: Response, admin: bool | None = False, Authorize: AuthPASETO = Depends()) -> ClientModels.ClientModel: + """Create a new API client. + + Returns: + json: client information + """ + + Authorize.paseto_required() + + admin_claim: dict[str, bool] = {"admin": False} + + current_user: str | int | None = Authorize.get_subject() + + if 'admin' in Authorize.get_token_payload(): + admin_claim = {"admin": Authorize.get_token_payload()['admin']} + + if ( await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()) and + admin_claim['admin'] == True): + + client: ClientModels.ClientModel = await clients.generate(admin=admin) + await clients.store(client) + + return client + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + + +@app.delete('/client/{client_id}', response_model=ResponseModels.ClientDeletedResponse, status_code=status.HTTP_200_OK, tags=['Clients']) +@limiter.limit(config['slowapi']['limit']) +async def delete_client(request: Request, response: Response, client_id: str, Authorize: AuthPASETO = Depends()) -> dict: + """Delete an API client. + + Returns: + json: deletion status + """ + + Authorize.paseto_required() + + admin_claim: dict[str, bool] = {"admin": False} + + current_user: str | int | None = Authorize.get_subject() + + if 'admin' in Authorize.get_token_payload(): + admin_claim = {"admin": Authorize.get_token_payload()['admin']} + + if ( await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()) and + ( admin_claim['admin'] == True or + current_user == client_id ) ): + + if await clients.exists(client_id): + return {"id": client_id, "deleted": await clients.delete(client_id)} + else: + raise HTTPException(status_code=404, detail={ + "error": GeneralErrors.ClientNotFound().error, + "message": GeneralErrors.ClientNotFound().message + } + ) + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + +@app.patch('/client/{client_id}/secret', response_model=ResponseModels.ClientSecretUpdatedResponse, status_code=status.HTTP_200_OK, tags=['Clients']) +@limiter.limit(config['slowapi']['limit']) +async def update_client(request: Request, response: Response, client_id: str, Authorize: AuthPASETO = Depends()) -> dict: + """Update an API client's secret. + + Returns: + json: client ID and secret + """ + + Authorize.paseto_required() + + admin_claim: dict[str, bool] = {"admin": False} + + current_user: str | int | None = Authorize.get_subject() + + if 'admin' in Authorize.get_token_payload(): + admin_claim = {"admin": Authorize.get_token_payload()['admin']} + + if ( await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()) and + ( admin_claim['admin'] == True or + current_user == client_id ) ): + + if await clients.exists(client_id): + new_secret: str = await generators.generate_secret() + + if await clients.update_secret(client_id, new_secret): + return {"id": client_id, "secret": new_secret} + else: + raise HTTPException(status_code=500, detail={ + "error": GeneralErrors.InternalServerError().error, + "message": GeneralErrors.InternalServerError().message + } + ) + else: + raise HTTPException(status_code=404, detail={ + "error": GeneralErrors.ClientNotFound().error, + "message": GeneralErrors.ClientNotFound().message + } + ) + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + +@app.patch('/client/{client_id}/status', response_model=ResponseModels.ClientStatusResponse, status_code=status.HTTP_200_OK, tags=['Clients']) +async def client_status(request: Request, response: Response, client_id: str, active: bool, Authorize: AuthPASETO = Depends()) -> dict: + """Activate or deactivate a client + + Returns: + json: json response containing client ID and activation status + """ + + Authorize.paseto_required() + + admin_claim: dict[str, bool] = {"admin": False} + + current_user: str | int | None = Authorize.get_subject() + + if 'admin' in Authorize.get_token_payload(): + admin_claim = {"admin": Authorize.get_token_payload()['admin']} + + if ( await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()) and + ( admin_claim['admin'] == True or + current_user == client_id ) ): + + if await clients.exists(client_id): + if await clients.status(client_id, active): + return {"id": client_id, "active": active} + else: + raise HTTPException(status_code=500, detail={ + "error": GeneralErrors.InternalServerError().error, + "message": GeneralErrors.InternalServerError().message + } + ) + else: + raise HTTPException(status_code=404, detail={ + "error": GeneralErrors.ClientNotFound().error, + "message": GeneralErrors.ClientNotFound().message + } + ) + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + + +@app.post('/announcement', response_model=AnnouncementModels.AnnouncementCreatedResponse, + status_code=status.HTTP_201_CREATED, tags=['Announcements']) +@limiter.limit(config['slowapi']['limit']) +async def create_announcement(request: Request, response: Response, + announcement: AnnouncementModels.AnnouncementCreateModel, + Authorize: AuthPASETO = Depends()) -> dict: + """Create a new announcement. + + Returns: + json: announcement information + """ + Authorize.paseto_required() + + if await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()): + announcement_created: bool = await announcements.store(announcement=announcement, + author=Authorize.get_subject()) + + if announcement_created: + return {"created": announcement_created} + else: + raise HTTPException(status_code=500, detail={ + "error": GeneralErrors.InternalServerError().error, + "message": GeneralErrors.InternalServerError().message + } + ) + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + +@app.get('/announcement', response_model=AnnouncementModels.AnnouncementModel, tags=['Announcements']) +@limiter.limit(config['slowapi']['limit']) +async def get_announcement(request: Request, response: Response) -> dict: + """Get an announcement. + + Returns: + json: announcement information + """ + if await announcements.exists(): + return await announcements.get() + else: + raise HTTPException(status_code=404, detail={ + "error": GeneralErrors.AnnouncementNotFound().error, + "message": GeneralErrors.AnnouncementNotFound().message + } + ) + +@app.delete('/announcement', + response_model=AnnouncementModels.AnnouncementDeleted, + status_code=status.HTTP_200_OK, tags=['Announcements']) +@limiter.limit(config['slowapi']['limit']) +async def delete_announcement(request: Request, response: Response, + Authorize: AuthPASETO = Depends()) -> dict: + """Delete an announcement. + + Returns: + json: deletion status + """ + + Authorize.paseto_required() + + if await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()): + if await announcements.exists(): + return {"deleted": await announcements.delete()} + else: + raise HTTPException(status_code=404, detail={ + "error": GeneralErrors.AnnouncementNotFound().error, + "message": GeneralErrors.AnnouncementNotFound().message + } + ) + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + +@app.post('/auth', response_model=ResponseModels.ClientAuthTokenResponse, status_code=status.HTTP_200_OK, tags=['Authentication']) +@limiter.limit(config['slowapi']['limit']) +async def auth(request: Request, response: Response, client: ClientModels.ClientAuthModel, Authorize: AuthPASETO = Depends()) -> dict: + """Authenticate a client and get an auth token. + + Returns: + access_token: auth token + refresh_token: refresh token + """ + + admin_claim: dict[str, bool] + + if await clients.exists(client.id): + authenticated: bool = await clients.authenticate(client.id, client.secret) + + if not authenticated: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + else: + if await clients.is_admin(client.id): + admin_claim = {"admin": True} + else: + admin_claim = {"admin": False} + + access_token = Authorize.create_access_token(subject=client.id, + user_claims=admin_claim, + fresh=True) + + refresh_token = Authorize.create_refresh_token(subject=client.id, + user_claims=admin_claim) + + return {"access_token": access_token, "refresh_token": refresh_token} + else: + raise HTTPException(status_code=401, detail={ + "error": GeneralErrors.Unauthorized().error, + "message": GeneralErrors.Unauthorized().message + } + ) + +@app.post('/auth/refresh', response_model=ResponseModels.ClientTokenRefreshResponse, + status_code=status.HTTP_200_OK, tags=['Authentication']) +@limiter.limit(config['slowapi']['limit']) +async def refresh(request: Request, response: Response, + Authorize: AuthPASETO = Depends()) -> dict: + """Refresh an auth token. + + Returns: + access_token: auth token + """ + + Authorize.paseto_required(refresh_token=True) + + admin_claim: dict[str, bool] = {"admin": False} + + current_user: str | int | None = Authorize.get_subject() + + if 'admin' in Authorize.get_token_payload(): + admin_claim = {"admin": Authorize.get_token_payload()['admin']} + + return {"access_token": Authorize.create_access_token(subject=current_user, + user_claims=admin_claim, + fresh=False)} @app.on_event("startup") async def startup() -> None: - redis_url = f"{redis_config['url']}:{redis_config['port']}/{redis_config['database']}" - redis = aioredis.from_url(redis_url, encoding="utf8", decode_responses=True) - FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache") + FastAPICache.init(RedisBackend(RedisConnector.connect(config['cache']['database'])), + prefix="fastapi-cache") return None # setup right before running to make sure no other library overwrites it -Logger.setup_logging(LOG_LEVEL=config["logging"]["level"], JSON_LOGS=config["logging"]["json_logs"]) - -# Run app -if __name__ == '__main__': - uvicorn.run(app, host=config['uvicorn']['host'], port=config['uvicorn']['port']) \ No newline at end of file +Logger.setup_logging(LOG_LEVEL=config["logging"]["level"], + JSON_LOGS=config["logging"]["json_logs"]) \ No newline at end of file diff --git a/modules/models/ResponseModels.py b/modules/models/ResponseModels.py deleted file mode 100644 index d726b1d..0000000 --- a/modules/models/ResponseModels.py +++ /dev/null @@ -1,41 +0,0 @@ -from pydantic import BaseModel -import modules.models.ResponseFields as ResponseFields - -"""Implements pydantic models and model generator for the API's responses.""" - -class ToolsResponseModel(BaseModel): - """Implements the JSON response model for the /tools endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - tools: list[ ResponseFields.ToolsResponseFields ] - -class PatchesResponseModel(BaseModel): - """Implements the JSON response model for the /patches endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - __root__: list[ ResponseFields.PatchesResponseFields ] - -class ContributorsResponseModel(BaseModel): - """Implements the JSON response model for the /contributors endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - repositories: list[ ResponseFields.ContributorsResponseFields ] - -class PingResponseModel(BaseModel): - """Implements the JSON response model for the /heartbeat endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - status: int - detail: str diff --git a/mypy.ini b/mypy.ini index 7529c41..35dd8a9 100644 --- a/mypy.ini +++ b/mypy.ini @@ -55,4 +55,16 @@ ignore_missing_imports = True [mypy-redis.*] # No stubs available +ignore_missing_imports = True + +[mypy-toolz.*] +# No stubs available +ignore_missing_imports = True + +[mypy-fastapi_paseto_auth.*] +# No stubs available +ignore_missing_imports = True + +[mypy-aiofiles.*] +# No stubs available ignore_missing_imports = True \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index b7999e0..7527fcb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,18 +1,10 @@ [[package]] -name = "aioredis" -version = "2.0.1" -description = "asyncio (PEP 3156) Redis support" +name = "aiofiles" +version = "22.1.0" +description = "File support for asyncio." category = "main" optional = false -python-versions = ">=3.6" - -[package.dependencies] -async-timeout = "*" -hiredis = {version = ">=1.0", optional = true, markers = "implementation_name == \"cpython\" and extra == \"hiredis\""} -typing-extensions = "*" - -[package.extras] -hiredis = ["hiredis (>=1.0)"] +python-versions = ">=3.7,<4.0" [[package]] name = "aiorwlock" @@ -39,6 +31,37 @@ doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 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)"] trio = ["trio (>=0.16)"] +[[package]] +name = "argon2-cffi" +version = "21.3.0" +description = "The secure Argon2 password hashing algorithm." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["cogapp", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pytest", "sphinx", "sphinx-notfound-page", "tomli"] +docs = ["furo", "sphinx", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + [[package]] name = "async-timeout" version = "4.0.2" @@ -69,6 +92,17 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "click" version = "8.1.3" @@ -88,6 +122,39 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "cryptography" +version = "37.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools_rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "cytoolz" +version = "0.12.0" +description = "Cython implementation of Toolz: High performance functional utilities" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + [[package]] name = "deprecated" version = "1.2.13" @@ -139,6 +206,24 @@ dynamodb = ["aiobotocore (>=1.4.1,<2.0.0)"] memcache = ["aiomcache"] redis = ["redis (>=4.2.0rc1,<5.0.0)"] +[[package]] +name = "fastapi-paseto-auth" +version = "0.6.0" +description = "FastAPI extension that provides PASETO Auth support" +category = "main" +optional = false +python-versions = ">=3.10" + +[package.dependencies] +fastapi = ">=0.65.3,<=0.85.0" +pydantic = ">=1.9.1,<=1.10.2" +pyseto = ">=1.6.9,<=1.6.10" + +[package.extras] +dev = ["uvicorn (>=0.11.5)"] +doc = ["markdown-include (>=0.7.0,<0.8.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.3.9,<9.0.0)"] +test = ["coveralls (==3.3.1)", "pytest (==7.1.3)", "pytest-cov (==3.0.0)"] + [[package]] name = "fasteners" version = "0.17.3" @@ -201,17 +286,6 @@ sniffio = ">=1.0.0,<2.0.0" http2 = ["h2 (>=3,<5)"] socks = ["socksio (>=1.0.0,<2.0.0)"] -[[package]] -name = "httptools" -version = "0.5.0" -description = "A collection of framework independent HTTP protocol utils." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - [[package]] name = "httpx" version = "0.23.0" @@ -249,6 +323,28 @@ fasteners = ">=0.16.3,<0.18.0" httpx = ">=0.23.0,<0.24.0" msgpack = ">=1.0.3,<2.0.0" +[[package]] +name = "hypercorn" +version = "0.14.3" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +toml = "*" +uvloop = {version = "*", optional = true, markers = "platform_system != \"Windows\" and extra == \"uvloop\""} +wsproto = ">=0.14.0" + +[package.extras] +docs = ["pydata-sphinx-theme"] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["trio (>=0.11.0)"] +uvloop = ["uvloop"] + [[package]] name = "hyperframe" version = "6.0.1" @@ -265,6 +361,14 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "iso8601" +version = "1.1.0" +description = "Simple module to parse ISO 8601 dates" +category = "main" +optional = false +python-versions = ">=3.6.2,<4.0" + [[package]] name = "limits" version = "1.6" @@ -301,7 +405,7 @@ python-versions = "*" [[package]] name = "mypy" -version = "0.981" +version = "0.982" description = "Optional static typing for Python" category = "dev" optional = false @@ -344,6 +448,23 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +argon2-cffi = {version = ">=18.2.0", optional = true, markers = "extra == \"argon2\""} + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] +totp = ["cryptography"] + [[package]] name = "pendulum" version = "2.1.2" @@ -356,6 +477,30 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-dateutil = ">=2.6,<3.0" pytzdata = ">=2020.1" +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycryptodomex" +version = "3.15.0" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "pydantic" version = "1.10.2" @@ -382,6 +527,23 @@ python-versions = ">=3.6.8" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyseto" +version = "1.6.10" +description = "A Python implementation of PASETO/PASERK." +category = "main" +optional = false +python-versions = ">=3.6.2,<4.0.0" + +[package.dependencies] +cryptography = ">=36,<38" +iso8601 = ">=1.0.2,<2.0.0" +passlib = {version = ">=1.7.4,<2.0.0", extras = ["argon2"]} +pycryptodomex = ">=3.12.0,<4.0.0" + +[package.extras] +docs = ["Sphinx[docs] (>=4.3.2,<6.0.0)", "sphinx-autodoc-typehints[docs] (==1.12.0)", "sphinx-rtd-theme[docs] (>=1.0.0,<2.0.0)"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -393,17 +555,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" -[[package]] -name = "python-dotenv" -version = "0.21.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -cli = ["click (>=5.0)"] - [[package]] name = "pytzdata" version = "2020.1" @@ -412,14 +563,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "redis" version = "4.3.4" @@ -453,7 +596,7 @@ idna2008 = ["idna"] [[package]] name = "sentry-sdk" -version = "1.9.9" +version = "1.9.10" description = "Python client for Sentry (https://sentry.io)" category = "main" optional = false @@ -540,6 +683,22 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "toolz" +version = "0.12.0" +description = "List processing tools and functional utilities" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "types-redis" +version = "4.3.21.1" +description = "Typing stubs for redis" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-toml" version = "0.10.8" @@ -550,12 +709,20 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "4.3.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "ujson" +version = "5.5.0" +description = "Ultra fast JSON encoder and decoder for Python" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "urllib3" version = "1.26.12" @@ -579,14 +746,7 @@ python-versions = ">=3.7" [package.dependencies] click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.4.0", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -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\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] @@ -604,25 +764,6 @@ dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flak 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)"] 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)"] -[[package]] -name = "watchfiles" -version = "0.17.0" -description = "Simple, modern and high performance file watching and code reload in python." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -anyio = ">=3.0.0,<4" - -[[package]] -name = "websockets" -version = "10.3" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "win32-setctime" version = "1.1.0" @@ -642,15 +783,26 @@ category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +h11 = ">=0.9.0,<1" + [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "d684900a678c127ba7c0c83ec6820f92f63f7d3bbc94f516610bce11bdea6cd1" +content-hash = "b35d9d99689d712256be20c9124a3786a777bcbb9854a57c76ee875a6a812931" [metadata.files] -aioredis = [ - {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, - {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, +aiofiles = [ + {file = "aiofiles-22.1.0-py3-none-any.whl", hash = "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad"}, + {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"}, ] aiorwlock = [ {file = "aiorwlock-1.3.0-py3-none-any.whl", hash = "sha256:45baf8e4fa9a23e0bb325fbd67da80de1fd7ae1d4f59a6381754c60cec7b289b"}, @@ -660,6 +812,33 @@ anyio = [ {file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"}, {file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"}, ] +argon2-cffi = [ + {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, + {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, +] +argon2-cffi-bindings = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -672,6 +851,72 @@ certifi = [ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -680,6 +925,116 @@ colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] +cryptography = [ + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, +] +cytoolz = [ + {file = "cytoolz-0.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8237612fed78d4580e94141a74ac0977f5a9614dd7fa8f3d2fcb30e6d04e73aa"}, + {file = "cytoolz-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:798dff7a40adbb3dfa2d50499c2038779061ebc37eccedaf28fa296cb517b84e"}, + {file = "cytoolz-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:336551092eb1cfc2ad5878cc08ef290f744843f84c1dda06f9e4a84d2c440b73"}, + {file = "cytoolz-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79b46cda959f026bd9fc33b4046294b32bd5e7664a4cf607179f80ac93844e7f"}, + {file = "cytoolz-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b716f66b5ee72dbf9a001316ffe72afe0bb8f6ce84e341aec64291c0ff16b9f4"}, + {file = "cytoolz-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac7758c5c5a66664285831261a9af8e0af504026e0987cd01535045945df6e1"}, + {file = "cytoolz-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:337c9a3ce2929c6361bcc1b304ce81ed675078a34c203dbb7c3e154f7ed1cca8"}, + {file = "cytoolz-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee1fe1a3d0c8c456c3fbf62f28d178f870d14302fcd1edbc240b717ae3ab08de"}, + {file = "cytoolz-0.12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f1f5c1ef04240b323b9e6b87d4b1d7f14b735e284a33b18a509537a10f62715c"}, + {file = "cytoolz-0.12.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25c037a7b4f49730ccc295a03cd2217ba67ff43ac0918299f5f368271433ff0f"}, + {file = "cytoolz-0.12.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:38e3386f63ebaea46a4ee0bfefc9a38590c3b78ab86439766b5225443468a76b"}, + {file = "cytoolz-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb072fa81caab93a5892c4b69dfe0d48f52026a7fe83ba2567020a7995a456e7"}, + {file = "cytoolz-0.12.0-cp310-cp310-win32.whl", hash = "sha256:a4acf6cb20f01a5eb5b6d459e08fb92aacfb4de8bc97e25437c1a3e71860b452"}, + {file = "cytoolz-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:7fe93ffde090e2867f8ce4369d0c1abf5651817a74a3d0a4da2b1ffd412603ff"}, + {file = "cytoolz-0.12.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d212296e996a70db8d9e1c0622bc8aefa732eb0416b5441624d0fd5b853ea391"}, + {file = "cytoolz-0.12.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:231d87ffb5fc468989e35336a2f8da1c9b8d97cfd9300cf2df32e953e4d20cae"}, + {file = "cytoolz-0.12.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f26079bc2d0b7aa1a185516ac9f7cda0d7932da6c60589bfed4079e3a5369e83"}, + {file = "cytoolz-0.12.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d511dd49eb1263ccb4e5f84ae1478dc2824d66b813cdf700e1ba593faa256ade"}, + {file = "cytoolz-0.12.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa5ded9f811c36668239adb4806fca1244b06add4d64af31119c279aab1ef8a6"}, + {file = "cytoolz-0.12.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c818a382b828e960fbbedbc85663414edbbba816c2bf8c1bb5651305d79bdb97"}, + {file = "cytoolz-0.12.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1c22255e7458feb6f43d99c9578396e91d5934757c552128f6afd3b093b41c00"}, + {file = "cytoolz-0.12.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5b7079b3197256ac6bf73f8b9484d514fac68a36d05513b9e5247354d6fc2885"}, + {file = "cytoolz-0.12.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:2ee9ca2cfc939607926096c7cc6f298cee125f8ca53a4f46745f8dfbb7fb7ab1"}, + {file = "cytoolz-0.12.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:8c0101bb2b2bcc0de2e2eb288a132c261e5fa883b1423799b47d4f0cfd879cd6"}, + {file = "cytoolz-0.12.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4b8b1d9764d08782caa8ba0e91d76b95b973a82f4ce2a3f9c7e726bfeaddbdfa"}, + {file = "cytoolz-0.12.0-cp36-cp36m-win32.whl", hash = "sha256:f71b49a41826a8e7fd464d6991134a6d022a666be4e76d517850abbea561c909"}, + {file = "cytoolz-0.12.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ae7f417bb2b4e3906e525b3dbe944791dfa9248faea719c7a9c200aa1a019a4e"}, + {file = "cytoolz-0.12.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b05dc257996c0accf6f877b1f212f74dc134b39c46baac09e1894d9d9c970b6a"}, + {file = "cytoolz-0.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2cca43caea857e761cc458ffb4f7af397a13824c5e71341ca08035ff5ff0b27"}, + {file = "cytoolz-0.12.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd840adfe027d379e7aede973bc0e193e6eef9b33d46d1d42826e26db9b37d7e"}, + {file = "cytoolz-0.12.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b067c88de0eaca174211c8422b3f72cbfb63b101a0eeb528c4f21282ca0afe"}, + {file = "cytoolz-0.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db619f17705067f1f112d3e84a0904b2f04117e50cefc4016f435ff0dc59bc4e"}, + {file = "cytoolz-0.12.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e8335998e21205574fc7d8d17844a9cc0dd4cbb25bb7716d90683a935d2c879"}, + {file = "cytoolz-0.12.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:46b9f4af719b113c01a4144c52fc4b929f98a47017a5408e3910050f4641126b"}, + {file = "cytoolz-0.12.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d29cf7a44a8abaeb00537e3bad7abf823fce194fe707c366f81020d384e22f7"}, + {file = "cytoolz-0.12.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02dc4565a8d27c9f3e87b715c0a300890e17c94ba1294af61c4ba97aa8482b22"}, + {file = "cytoolz-0.12.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2bd1c692ab706acb46cfebe7105945b07f7274598097e32c8979d3b22ae62cc6"}, + {file = "cytoolz-0.12.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d035805dcdefcdfe64d97d6e1e7603798588d5e1ae08e61a5dae3258c3cb407a"}, + {file = "cytoolz-0.12.0-cp37-cp37m-win32.whl", hash = "sha256:9ecdd6e2be8d59b76c2bd3e2d832e7b3d5b2535c418b13cfa85e3b17de985199"}, + {file = "cytoolz-0.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3a5408a74df84e84aa1c86a2f9f2ffaed51a55f34bbad5b8fae547cb9167e977"}, + {file = "cytoolz-0.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1cf9ae77eed57924becd3ab65ae24487d7b1f9823d3e685d796e58f57424f82a"}, + {file = "cytoolz-0.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dc8df9adfca0da9956589f53764d459389ce86d824663c7217422232f1dfbc9d"}, + {file = "cytoolz-0.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf460dc6bed081f274cd3d8ae162dd7e382014161d65edcdec832035d93901b"}, + {file = "cytoolz-0.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5784adcdb285e70b61efc1a369cd61c6b7f1e0b5d521651f93cde09549681f5"}, + {file = "cytoolz-0.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09fac69cebcb79a6ed75565fe2de9511be6e3d93f30dad115832cc1a3933b6ce"}, + {file = "cytoolz-0.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1744217505b835fcf55d82d67addd0d361791c4fd6a2f485f034b343ffc7edb3"}, + {file = "cytoolz-0.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fa49cfaa0eedad59d8357a482bd10e2cc2a12ad9f41aae53427e82d3eba068a"}, + {file = "cytoolz-0.12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c9f8c9b3cfa20b4ce6a89b7e2e7ffda76bdd81e95b7d20bbb2c47c2b31e72622"}, + {file = "cytoolz-0.12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9fe89548b1dc7c8b3160758d192791b32bd42b1c244a20809a1053a9d74428"}, + {file = "cytoolz-0.12.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d61bc1713662e7d9aa3e298dad790dfd027c5c0f1342c36be8401aebe3d3d453"}, + {file = "cytoolz-0.12.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:69c04ae878d5bcde5462e7290f950bfce11fd139ec4b481687983326658e6dbe"}, + {file = "cytoolz-0.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:21986f4a970c03ca84806b3a08e89386ac4aeb54c9b79d6a7268e83225331a87"}, + {file = "cytoolz-0.12.0-cp38-cp38-win32.whl", hash = "sha256:e17516a102731bcf86446ce148127a8cd2887cf27ac388990cd63881115b4fdc"}, + {file = "cytoolz-0.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:16748ea2b40c5978190d9acf9aa8fbacbfb440964c1035dc16cb14dbd557edb5"}, + {file = "cytoolz-0.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:02583c9fd4668f9e343ad4fc0e0f9651b1a0c16fe92bd208d07fd07de90fdc99"}, + {file = "cytoolz-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee92dadb312e657b9b666a0385fafc6dad073d8a0fbef5cea09e21011554206a"}, + {file = "cytoolz-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12d3d11ceb0fce8be5463f1e363366888c4b71e68fb2f5d536e4790b933cfd7e"}, + {file = "cytoolz-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f87472837c26b3bc91f9767c7adcfb935d0c097937c6744250672cd8c36019d"}, + {file = "cytoolz-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7244fb0d0b87499becc29051b82925e0daf3838e6c352e6b2d62e0f969b090af"}, + {file = "cytoolz-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deb8550f487de756f1c24c56fa2c8451a53c0346868c13899c6b3a39b1f3d2c3"}, + {file = "cytoolz-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f959c1319b7e6ed3367b0f5a54a7b9c59063bd053c74278b27999db013e568df"}, + {file = "cytoolz-0.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f40897f6f341e03a945759fcdb2208dc7c64dc312386d3088c47b78fca2a3b2"}, + {file = "cytoolz-0.12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:68336dfbe00efebbb1d02b8aa00b570dceec5d03fbd818c620aa246a8f5e5409"}, + {file = "cytoolz-0.12.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:886b3bf8fa99510836107097a5e5a2bd81631d3795dedc5684e25bef6538ac39"}, + {file = "cytoolz-0.12.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f94b4a3500345de5853d1896b7e770ce4a6577a431f43ff7d8f05f9051aeb7d"}, + {file = "cytoolz-0.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9dd7dbdfc24ed309af96be170c9030f43713950afab2b4bed1d372a91b37cbb0"}, + {file = "cytoolz-0.12.0-cp39-cp39-win32.whl", hash = "sha256:ed8771e36430fb0e4398030569bdab1419e4e74f7bcd51ea57239aa95441983a"}, + {file = "cytoolz-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:a15157f4280f6e5d7c2d0892847a6c4dffbd2c5cefccaf1ac1f1c6c3d2cf9936"}, + {file = "cytoolz-0.12.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ae403cac13c2b9a2a92e56468ca1f822899b64d75d5be8ca802f1c14870d9133"}, + {file = "cytoolz-0.12.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ff74cb0e1a50de7f59e54a156dfd734b6593008f6f804d0726a73b89d170cd"}, + {file = "cytoolz-0.12.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f24e70d29223cde8ce3f5aefa7fd06bda12ae4386dcfbc726773e95b099cde0d"}, + {file = "cytoolz-0.12.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a79658fd264c5f82ea1b5cb45cf3899afabd9ec3e58c333bea042a2b4a94134"}, + {file = "cytoolz-0.12.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ef4a496a3175aec595ae24ad03e0bb2fe76401f8f79e7ef3d344533ba990ec0e"}, + {file = "cytoolz-0.12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bb0fc2ed8efa89f31ffa99246b1d434ff3db2b7b7e35147486172da849c8024a"}, + {file = "cytoolz-0.12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59263f296e043d4210dd34f91e6f11c4b20e6195976da23170d5ad056030258a"}, + {file = "cytoolz-0.12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:274bc965cd93d6fa0bfe6f770cf6549bbe58d7b0a48dd6893d3f2c4b495d7f95"}, + {file = "cytoolz-0.12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8feb4d056c22983723278160aff8a28c507b0e942768f4e856539a60e7bb874"}, + {file = "cytoolz-0.12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09f5652caeac85e3735bd5aaed49ebf4eeb7c0f15cb9b7c4a5fb6f45308dc2fd"}, + {file = "cytoolz-0.12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8060be3b1fa24a4e3b165ce3c0ee6048f5e181289af57dbd9e3c4d4b8545dd78"}, + {file = "cytoolz-0.12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e32292721f16516a574891a1af6760cba37a0f426a2b2cea6f9d560131a76ea"}, + {file = "cytoolz-0.12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aade6ebb4507330b0540af58dc2804415945611e90c70bb97360973e487c48a"}, + {file = "cytoolz-0.12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f909760f89a54d860cf960b4cd828f9f6301fb104cd0de5b15b16822c9c4828b"}, + {file = "cytoolz-0.12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8e69c9f3a32e0f9331cf6707a0f159c6dec0ff2a9f41507f6b2d06cd423f0d0"}, + {file = "cytoolz-0.12.0.tar.gz", hash = "sha256:c105b05f85e03fbcd60244375968e62e44fe798c15a3531c922d531018d22412"}, +] deprecated = [ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, @@ -692,6 +1047,10 @@ fastapi-cache2 = [ {file = "fastapi-cache2-0.1.9.tar.gz", hash = "sha256:816612f7b29b4ea4ed3b4e03c55b7f96b4e4d6dffce6a95e2cf5cf36a980eaaa"}, {file = "fastapi_cache2-0.1.9-py3-none-any.whl", hash = "sha256:5b6f32bc8e786d9fffe4f3ef343861cab276acc64fb5d8d09077524743ad3702"}, ] +fastapi-paseto-auth = [ + {file = "fastapi-paseto-auth-0.6.0.tar.gz", hash = "sha256:28ee4b386c86158b98d6478be632c4b26c9c86f457cd5bb76eb33bffafa1f54f"}, + {file = "fastapi_paseto_auth-0.6.0-py3-none-any.whl", hash = "sha256:53cebf3eca29305c786891e3c0180e19427245daa9f8bec23445041451921eed"}, +] fasteners = [ {file = "fasteners-0.17.3-py3-none-any.whl", hash = "sha256:cae0772df265923e71435cc5057840138f4e8b6302f888a567d06ed8e1cbca03"}, {file = "fasteners-0.17.3.tar.gz", hash = "sha256:a9a42a208573d4074c77d041447336cf4e3c1389a256fd3e113ef59cf29b7980"}, @@ -755,49 +1114,6 @@ httpcore = [ {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, ] -httptools = [ - {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, - {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, - {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, - {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"}, - {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, - {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, - {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, - {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, - {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, - {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, - {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"}, - {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, - {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, - {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, - {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, - {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, - {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"}, - {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, - {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, - {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, - {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, - {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, - {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"}, - {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, - {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, - {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, - {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, - {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, - {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, - {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"}, - {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, - {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, - {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, - {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, - {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, - {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, - {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"}, - {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, - {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, - {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, - {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, -] httpx = [ {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, @@ -806,6 +1122,10 @@ httpx-cache = [ {file = "httpx-cache-0.6.0.tar.gz", hash = "sha256:3d136ad207d8004e59a69283aa6fc40e1190dad5edbde5859b37778f6d1ecdbf"}, {file = "httpx_cache-0.6.0-py3-none-any.whl", hash = "sha256:2b548d68fa55159e2fdc49ea151f513217c21cb4f0057e79fec8376ce15bfe7a"}, ] +hypercorn = [ + {file = "Hypercorn-0.14.3-py3-none-any.whl", hash = "sha256:7c491d5184f28ee960dcdc14ab45d14633ca79d72ddd13cf4fcb4cb854d679ab"}, + {file = "Hypercorn-0.14.3.tar.gz", hash = "sha256:4a87a0b7bbe9dc75fab06dbe4b301b9b90416e9866c23a377df21a969d6ab8dd"}, +] hyperframe = [ {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, @@ -814,6 +1134,10 @@ idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +iso8601 = [ + {file = "iso8601-1.1.0-py3-none-any.whl", hash = "sha256:8400e90141bf792bce2634df533dc57e3bee19ea120a87bebcd3da89a58ad73f"}, + {file = "iso8601-1.1.0.tar.gz", hash = "sha256:32811e7b81deee2063ea6d2e94f8819a86d1f3811e49d23623a41fa832bef03f"}, +] limits = [ {file = "limits-1.6-py3-none-any.whl", hash = "sha256:12ae4449cf7daadee43edf4096acd9cb9f4bfdec3a995aa9fbd0f72b0b9af762"}, {file = "limits-1.6.tar.gz", hash = "sha256:6c0a57b42647f1141f5a7a0a8479b49e4367c24937a01bd9d4063a595c2dd48a"}, @@ -877,30 +1201,30 @@ msgpack = [ {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, ] mypy = [ - {file = "mypy-0.981-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4bc460e43b7785f78862dab78674e62ec3cd523485baecfdf81a555ed29ecfa0"}, - {file = "mypy-0.981-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:756fad8b263b3ba39e4e204ee53042671b660c36c9017412b43af210ddee7b08"}, - {file = "mypy-0.981-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16a0145d6d7d00fbede2da3a3096dcc9ecea091adfa8da48fa6a7b75d35562d"}, - {file = "mypy-0.981-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce65f70b14a21fdac84c294cde75e6dbdabbcff22975335e20827b3b94bdbf49"}, - {file = "mypy-0.981-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e35d764784b42c3e256848fb8ed1d4292c9fc0098413adb28d84974c095b279"}, - {file = "mypy-0.981-cp310-cp310-win_amd64.whl", hash = "sha256:e53773073c864d5f5cec7f3fc72fbbcef65410cde8cc18d4f7242dea60dac52e"}, - {file = "mypy-0.981-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ee196b1d10b8b215e835f438e06965d7a480f6fe016eddbc285f13955cca659"}, - {file = "mypy-0.981-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad21d4c9d3673726cf986ea1d0c9fb66905258709550ddf7944c8f885f208be"}, - {file = "mypy-0.981-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1debb09043e1f5ee845fa1e96d180e89115b30e47c5d3ce53bc967bab53f62d"}, - {file = "mypy-0.981-cp37-cp37m-win_amd64.whl", hash = "sha256:9f362470a3480165c4c6151786b5379351b790d56952005be18bdbdd4c7ce0ae"}, - {file = "mypy-0.981-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c9e0efb95ed6ca1654951bd5ec2f3fa91b295d78bf6527e026529d4aaa1e0c30"}, - {file = "mypy-0.981-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e178eaffc3c5cd211a87965c8c0df6da91ed7d258b5fc72b8e047c3771317ddb"}, - {file = "mypy-0.981-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:06e1eac8d99bd404ed8dd34ca29673c4346e76dd8e612ea507763dccd7e13c7a"}, - {file = "mypy-0.981-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa38f82f53e1e7beb45557ff167c177802ba7b387ad017eab1663d567017c8ee"}, - {file = "mypy-0.981-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:64e1f6af81c003f85f0dfed52db632817dabb51b65c0318ffbf5ff51995bbb08"}, - {file = "mypy-0.981-cp38-cp38-win_amd64.whl", hash = "sha256:e1acf62a8c4f7c092462c738aa2c2489e275ed386320c10b2e9bff31f6f7e8d6"}, - {file = "mypy-0.981-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b6ede64e52257931315826fdbfc6ea878d89a965580d1a65638ef77cb551f56d"}, - {file = "mypy-0.981-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eb3978b191b9fa0488524bb4ffedf2c573340e8c2b4206fc191d44c7093abfb7"}, - {file = "mypy-0.981-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f8fcf7b4b3cc0c74fb33ae54a4cd00bb854d65645c48beccf65fa10b17882c"}, - {file = "mypy-0.981-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64d2ce043a209a297df322eb4054dfbaa9de9e8738291706eaafda81ab2b362"}, - {file = "mypy-0.981-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ee3dbc53d4df7e6e3b1c68ac6a971d3a4fb2852bf10a05fda228721dd44fae1"}, - {file = "mypy-0.981-cp39-cp39-win_amd64.whl", hash = "sha256:8e8e49aa9cc23aa4c926dc200ce32959d3501c4905147a66ce032f05cb5ecb92"}, - {file = "mypy-0.981-py3-none-any.whl", hash = "sha256:794f385653e2b749387a42afb1e14c2135e18daeb027e0d97162e4b7031210f8"}, - {file = "mypy-0.981.tar.gz", hash = "sha256:ad77c13037d3402fbeffda07d51e3f228ba078d1c7096a73759c9419ea031bf4"}, + {file = "mypy-0.982-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5"}, + {file = "mypy-0.982-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3"}, + {file = "mypy-0.982-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c"}, + {file = "mypy-0.982-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6"}, + {file = "mypy-0.982-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046"}, + {file = "mypy-0.982-cp310-cp310-win_amd64.whl", hash = "sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e"}, + {file = "mypy-0.982-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20"}, + {file = "mypy-0.982-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947"}, + {file = "mypy-0.982-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40"}, + {file = "mypy-0.982-cp37-cp37m-win_amd64.whl", hash = "sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1"}, + {file = "mypy-0.982-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24"}, + {file = "mypy-0.982-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e"}, + {file = "mypy-0.982-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda"}, + {file = "mypy-0.982-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206"}, + {file = "mypy-0.982-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763"}, + {file = "mypy-0.982-cp38-cp38-win_amd64.whl", hash = "sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2"}, + {file = "mypy-0.982-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8"}, + {file = "mypy-0.982-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146"}, + {file = "mypy-0.982-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc"}, + {file = "mypy-0.982-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b"}, + {file = "mypy-0.982-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a"}, + {file = "mypy-0.982-cp39-cp39-win_amd64.whl", hash = "sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795"}, + {file = "mypy-0.982-py3-none-any.whl", hash = "sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d"}, + {file = "mypy-0.982.tar.gz", hash = "sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -954,6 +1278,10 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] +passlib = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] pendulum = [ {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, @@ -977,6 +1305,46 @@ pendulum = [ {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, ] +priority = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] +pycryptodomex = [ + {file = "pycryptodomex-3.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6f5b6ba8aefd624834bc177a2ac292734996bb030f9d1b388e7504103b6fcddf"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4540904c09704b6f831059c0dfb38584acb82cb97b0125cd52688c1f1e3fffa6"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0fadb9f7fa3150577800eef35f62a8a24b9ddf1563ff060d9bd3af22d3952c8c"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fc9bc7a9b79fe5c750fc81a307052f8daabb709bdaabb0fb18fb136b66b653b5"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f8be976cec59b11f011f790b88aca67b4ea2bd286578d0bd3e31bcd19afcd3e4"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:78d9621cf0ea35abf2d38fa2ca6d0634eab6c991a78373498ab149953787e5e5"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:b6306403228edde6e289f626a3908a2f7f67c344e712cf7c0a508bab3ad9e381"}, + {file = "pycryptodomex-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:48697790203909fab02a33226fda546604f4e2653f9d47bc5d3eb40879fa7c64"}, + {file = "pycryptodomex-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:18e2ab4813883ae63396c0ffe50b13554b32bb69ec56f0afaf052e7a7ae0d55b"}, + {file = "pycryptodomex-3.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3709f13ca3852b0b07fc04a2c03b379189232b24007c466be0f605dd4723e9d4"}, + {file = "pycryptodomex-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:191e73bc84a8064ad1874dba0ebadedd7cce4dedee998549518f2c74a003b2e1"}, + {file = "pycryptodomex-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e3164a18348bd53c69b4435ebfb4ac8a4076291ffa2a70b54f0c4b80c7834b1d"}, + {file = "pycryptodomex-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:5676a132169a1c1a3712edf25250722ebc8c9102aa9abd814df063ca8362454f"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e2b12968522a0358b8917fc7b28865acac002f02f4c4c6020fcb264d76bfd06d"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:e47bf8776a7e15576887f04314f5228c6527b99946e6638cf2f16da56d260cab"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:996e1ba717077ce1e6d4849af7a1426f38b07b3d173b879e27d5e26d2e958beb"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:65204412d0c6a8e3c41e21e93a5e6054a74fea501afa03046a388cf042e3377a"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:dd452a5af7014e866206d41751886c9b4bf379a339fdf2dbfc7dd16c0fb4f8e0"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:b9279adc16e4b0f590ceff581f53a80179b02cba9056010d733eb4196134a870"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-win32.whl", hash = "sha256:46b3f05f2f7ac7841053da4e0f69616929ca3c42f238c405f6c3df7759ad2780"}, + {file = "pycryptodomex-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:8eecdf9cdc7343001d047f951b9cc805cd68cb6cd77b20ea46af5bffc5bd3dfb"}, + {file = "pycryptodomex-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:67e1e6a92151023ccdfcfbc0afb3314ad30080793b4c27956ea06ab1fb9bcd8a"}, + {file = "pycryptodomex-3.15.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:c4cb9cb492ea7dcdf222a8d19a1d09002798ea516aeae8877245206d27326d86"}, + {file = "pycryptodomex-3.15.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:94c7b60e1f52e1a87715571327baea0733708ab4723346598beca4a3b6879794"}, + {file = "pycryptodomex-3.15.0-pp27-pypy_73-win32.whl", hash = "sha256:04cc393045a8f19dd110c975e30f38ed7ab3faf21ede415ea67afebd95a22380"}, + {file = "pycryptodomex-3.15.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0776bfaf2c48154ab54ea45392847c1283d2fcf64e232e85565f858baedfc1fa"}, + {file = "pycryptodomex-3.15.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:463119d7d22d0fc04a0f9122e9d3e6121c6648bcb12a052b51bd1eed1b996aa2"}, + {file = "pycryptodomex-3.15.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a07a64709e366c2041cd5cfbca592b43998bf4df88f7b0ca73dca37071ccf1bd"}, + {file = "pycryptodomex-3.15.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:35a8f7afe1867118330e2e0e0bf759c409e28557fb1fc2fbb1c6c937297dbe9a"}, + {file = "pycryptodomex-3.15.0.tar.gz", hash = "sha256:7341f1bb2dadb0d1a0047f34c3a58208a92423cdbd3244d998e4b28df5eac0ed"}, +] pydantic = [ {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, @@ -1019,53 +1387,18 @@ pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] +pyseto = [ + {file = "pyseto-1.6.10-py3-none-any.whl", hash = "sha256:b4d7b3f7766fc6c273b8c1f78374ceb41b4a5ea935bb67c6dd4b5443434e5e05"}, + {file = "pyseto-1.6.10.tar.gz", hash = "sha256:861b62c9103c3c2e9e955603a41d566675adf6681293a621b58b64aac8d7cf21"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-dotenv = [ - {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, - {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, -] pytzdata = [ {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, ] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {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"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {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"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {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"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {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"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {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"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] redis = [ {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, @@ -1075,8 +1408,8 @@ rfc3986 = [ {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] sentry-sdk = [ - {file = "sentry-sdk-1.9.9.tar.gz", hash = "sha256:d6c71d2f85710b66822adaa954af7912bab135d6c85febd5b0f3dfd4ab37e181"}, - {file = "sentry_sdk-1.9.9-py2.py3-none-any.whl", hash = "sha256:ef925b5338625448645a778428d8f22a3d17de8b28cc8e6fba60b93393ad86fe"}, + {file = "sentry-sdk-1.9.10.tar.gz", hash = "sha256:4fbace9a763285b608c06f01a807b51acb35f6059da6a01236654e08b0ee81ff"}, + {file = "sentry_sdk-1.9.10-py2.py3-none-any.whl", hash = "sha256:2469240f6190aaebcb453033519eae69cfe8cc602065b4667e18ee14fc1e35dc"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1102,13 +1435,88 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +toolz = [ + {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, + {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, +] +types-redis = [ + {file = "types-redis-4.3.21.1.tar.gz", hash = "sha256:493814829643fc04a14595eda6ccd69bdc0606477541ccda54238ce3f60bc993"}, + {file = "types_redis-4.3.21.1-py3-none-any.whl", hash = "sha256:65b8c842f406932218f8ce636f75e5a03cb6b382d3922cb3e5f87e127e6d434d"}, +] types-toml = [ {file = "types-toml-0.10.8.tar.gz", hash = "sha256:b7e7ea572308b1030dc86c3ba825c5210814c2825612ec679eb7814f8dd9295a"}, {file = "types_toml-0.10.8-py3-none-any.whl", hash = "sha256:8300fd093e5829eb9c1fba69cee38130347d4b74ddf32d0a7df650ae55c2b599"}, ] typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +ujson = [ + {file = "ujson-5.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff4928dc1e9704b567171c16787238201fdbf023665573c12c02146fe1e02eec"}, + {file = "ujson-5.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1dc2f46c31ef22b0aaa28cd71be897bea271e700636658d573df9c43c49ebbd0"}, + {file = "ujson-5.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6019e3480d933d3698f2ecb4b46d64bfadd64e718f04fac36e681f3254b49a93"}, + {file = "ujson-5.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5179088ef6487c475604b7898731a6ddeeada7702cfb2162155b016703a8475"}, + {file = "ujson-5.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c04ae27e076d81a3839047d8eed57c1e17e361640616fd520d752375e3ba8f0c"}, + {file = "ujson-5.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:60a4b481978ea2aad8fe8af1ecc271624d01b3cf4b09e9b643dd2fe19c07634c"}, + {file = "ujson-5.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a09d203983104918c62f2eef9406f24c355511f9217967df23e70fa7f5b54ff"}, + {file = "ujson-5.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b9812638d7aa8ecda2e8e1513fb4da999249603bffab7439a5f8f0bb362b0db"}, + {file = "ujson-5.5.0-cp310-cp310-win32.whl", hash = "sha256:33cd9084fefc74cbacf88c92fd260b61211e00bcde38d640c369e5dc34a2b4e1"}, + {file = "ujson-5.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:765d46f3d5e7a1d48075035e2d1a9164f683e3fccde834ca04602e6c588835bc"}, + {file = "ujson-5.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:278aa9d7cb56435c96d19f5d702e026bcf69f824e24b41e9b52706abd3565837"}, + {file = "ujson-5.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9585892091ae86045135d6a6129a644142d6a51b23e1428bb5de6d10bc0ce0c7"}, + {file = "ujson-5.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cdc46859024501c20ab74ad542cdf2f08b94b5ce384f2f569483fa3ed926d04"}, + {file = "ujson-5.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5bea13c73f36c4346808df3fa806596163a7962b6d28001ca2a391cab856089"}, + {file = "ujson-5.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3f4240d99d55eb97cb012e9adf401f5ed9cd827af0341ac44603832202b0d2"}, + {file = "ujson-5.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d93940664a5ccfd79f72dcb939b0c31a3479889f14f0eb95ec52976f8c0cae7d"}, + {file = "ujson-5.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:880c84ce59f49776cf120f77e7ca04877c97c6887917078dbc369eb47004d7cf"}, + {file = "ujson-5.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:977bf5be704a88d46bf5b228df8b44521b1f3119d741062191608b3a6a38f224"}, + {file = "ujson-5.5.0-cp311-cp311-win32.whl", hash = "sha256:e0b36257dc90194784531c3b922d8d31fb2b4d8e5adfd27aff4eee7174176365"}, + {file = "ujson-5.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:a34a5f034b339f69ef7f6a134c22d04b92e07b6ddc1dd65382e7e4ec65d6437d"}, + {file = "ujson-5.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f26544bc10c83a2ff9aa2e093500c1b473f327faae31fb468d591e5823333376"}, + {file = "ujson-5.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fd797a4837ba10671954e7c09010cec7aca67e09d193f4920a16beea5f66f65"}, + {file = "ujson-5.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d7cfac2547c93389fa303fc0c0eb6698825564e8389c41c9b60009c746207b6"}, + {file = "ujson-5.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4875cafc9a6482c04c7df52a725d1c41beb74913c0ff4ec8f189f1954a2afe9"}, + {file = "ujson-5.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0762a4fdf86e01f3f8d8b6b7158d01fdd870799ff3f402b676e358fcd879e7eb"}, + {file = "ujson-5.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6c7ae6e0778ab9610f5e80e0595957d101ab8de18c32a8c053a19943ef4831d0"}, + {file = "ujson-5.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:94874584b733a18b310b0e954d53168e62cd4a0fd9db85b1903f0902a7eb33e8"}, + {file = "ujson-5.5.0-cp37-cp37m-win32.whl", hash = "sha256:3b74467564814fbce322427a5664e6bcc7dae6dbc8acbef76300fe43ca4072ab"}, + {file = "ujson-5.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:59cdcd934385f36e8bd76aedc234371cc75c848d95bdce804ac8aa8744cfeffa"}, + {file = "ujson-5.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e506ecf89b6b9d304362ccef770831ec242a52c89dab1b4aabf1ab0eb1d5ed6"}, + {file = "ujson-5.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10095160dbe6bba8059ad6677a01da251431f4c68041bf796dcac0956b34f8f7"}, + {file = "ujson-5.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5035bb997d163f346c22abcec75190e7e756a5349e7c708bd3d5fd7066a9a854"}, + {file = "ujson-5.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d12f2d2df195c8c4e49d2cdbad640353a856c62ca2c624d8b47aa33b65a2a2"}, + {file = "ujson-5.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a485117f97312bef45f5d79d2ff97eff4da503b8a04f3691f59d31141686459"}, + {file = "ujson-5.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:21678d7e068707e4d54bdfeb8c250ebc548b51e499aed778b22112ca31a79669"}, + {file = "ujson-5.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a9b1320d8363a42d857fae8065a2174d38217cdd58cd8dc4f48d54e0591271e"}, + {file = "ujson-5.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:701e81e047f5c0cffd4ac828efca68b0bd270c616654966a051e9a5f836b385e"}, + {file = "ujson-5.5.0-cp38-cp38-win32.whl", hash = "sha256:1cef44ea4973344baed3d50a5da4a8843de3a6af7dea7fadf0a594e53ce5892f"}, + {file = "ujson-5.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e510d288e613d6927796dfb728e13e4530fc83b9ccac5888a21f7860486eab21"}, + {file = "ujson-5.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e1135264bcd40965cd35b0869e36952f54825024befdc7a923df9a7d83cfd800"}, + {file = "ujson-5.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:703fd69d9cb21d6ec2086789df9be2cf8140a76ff127050c24007ea8940dcd3b"}, + {file = "ujson-5.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:849f2ff40264152f25589cb48ddb4a43d14db811f841ec73989bfc0c8c4853fa"}, + {file = "ujson-5.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf416a93e1331820c77e3429df26946dbd4fe105e9b487cd2d1b7298b75784a8"}, + {file = "ujson-5.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:593a0f6fb0e186c5ba65465ed6f6215a30d1efa898c25e74de1c8577a1bff6d0"}, + {file = "ujson-5.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c20cc83b0df47129ec6ed8a47fa7dcfc309c5bad029464004162738502568bb"}, + {file = "ujson-5.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6f83be8257b2f2dd6dea5ee62cd28db90584da7a7af1fba77a2102fc7943638a"}, + {file = "ujson-5.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8141f654432cf75144d6103bfac2286b8adf23467201590b173a74535d6be22d"}, + {file = "ujson-5.5.0-cp39-cp39-win32.whl", hash = "sha256:3fe1aea596f9539fc20cd9e52f098c842afc090168824fd4ca9744fe13151a03"}, + {file = "ujson-5.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a655f7b755cfc5c07f2116b6dcf0ba148c89adef9a6d40c1b0f1fada878c4345"}, + {file = "ujson-5.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f19f11055ba2961eb39bdb1ff15763a53fca4fa0b5b624da3c7a528e83cdd09c"}, + {file = "ujson-5.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d87c817b292efb748f1974f37e8bb8a8772ef92f05f84e507159360814bcc3f"}, + {file = "ujson-5.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f9681ec4c60d0da590552427d770636d9079038c30b265f507ccde23caa7823"}, + {file = "ujson-5.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63d1ae1ca17bb2c847e298c7bcf084a73d56d434b4c50509fb93a4b4300b0b2"}, + {file = "ujson-5.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:95603eff711b8f3b9596e1c961dbeb745a792ba1904141612f194e07edd71e5f"}, + {file = "ujson-5.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2d90414e3b4b44b39825049185959488e084ea7fcaf6124afd5c00893938b09d"}, + {file = "ujson-5.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7471d4486f23518cff343f1eec6c68d1b977ed74c3e6cc3e1ac896b9b7d68645"}, + {file = "ujson-5.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee9a2c9a4b2421e77f8fe33ed0621dea03c66c710707553020b1e32f3afb6240"}, + {file = "ujson-5.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a8cb3c8637006c5bd8237ebb5992a76ba06e39988ad5cff2096227443e8fd6a"}, + {file = "ujson-5.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d9c89c521dc90c7564358e525f849b93ad1d710553c1491f66b8cce8113bc901"}, + {file = "ujson-5.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ab011e3556a9a1d9461bd686870c527327765ed02fe53550531d6609a8a33ff"}, + {file = "ujson-5.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:603607f56a0ee84d9cd2c7e9b1d29b18a70684b94ee34f07b9ffe8dc9c8a9f81"}, + {file = "ujson-5.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75bef34e69e7effb7b4849e3f830e3174d2cc6ec7273503fdde111c222dc9b3"}, + {file = "ujson-5.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abfe83e082c9208891e2158c1b5044a650ecec408b823bf6bf16cd7f8085cafa"}, + {file = "ujson-5.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4ef4ab8352861b99bd7fedb1fc6df3ea7f7d5216c789ba6d859e4ea06f1a4c45"}, + {file = "ujson-5.5.0.tar.gz", hash = "sha256:b25077a971c7da47bd6846a912a747f6963776d90720c88603b1b55d81790780"}, ] urllib3 = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, @@ -1150,76 +1558,6 @@ uvloop = [ {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, ] -watchfiles = [ - {file = "watchfiles-0.17.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:c7e1ffbd03cbcb46d1b7833e10e7d6b678ab083b4e4b80db06cfff5baca3c93f"}, - {file = "watchfiles-0.17.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:539bcdb55a487126776c9d8c011094214d1df3f9a2321a6c0b1583197309405a"}, - {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:00e5f307a58752ec1478eeb738863544bde21cc7a2728bd1c216060406bde9c1"}, - {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:92675f379a9d5adbc6a52179f3e39aa56944c6eecb80384608fff2ed2619103a"}, - {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dd1e3181ad5d83ca35e9147c72e24f39437fcdf570c9cdc532016399fb62957"}, - {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:204950f1d6083539af5c8b7d4f5f8039c3ce36fa692da12d9743448f3199cb15"}, - {file = "watchfiles-0.17.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:4056398d8f6d4972fe0918707b59d4cb84470c91d3c37f0e11e5a66c2a598760"}, - {file = "watchfiles-0.17.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ffff3418dc753a2aed2d00200a4daeaac295c40458f8012836a65555f288be8b"}, - {file = "watchfiles-0.17.0-cp37-abi3-win32.whl", hash = "sha256:b5c334cd3bc88aa4a8a1e08ec9f702b63c947211275defdc2dd79dc037fcb500"}, - {file = "watchfiles-0.17.0-cp37-abi3-win_amd64.whl", hash = "sha256:53a2faeb121bc51bb6b960984f46901227e2e2475acc5a8d4c905a600436752d"}, - {file = "watchfiles-0.17.0-cp37-abi3-win_arm64.whl", hash = "sha256:58dc3140dcf02a8aa76464a77a093016f10e89306fec21a4814922a64f3e8b9f"}, - {file = "watchfiles-0.17.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:adcf15ecc2182ea9d2358c1a8c2b53203c3909484918776929b7bbe205522c0e"}, - {file = "watchfiles-0.17.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:afd35a1bd3b9e68efe384ae7538481ae725597feb66f56f4bd23ecdbda726da0"}, - {file = "watchfiles-0.17.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2bdcae4c0f07ca6c090f5a2c30188cc6edba011b45e7c96eb1896648092367"}, - {file = "watchfiles-0.17.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:a53cb6c06e5c1f216c792fbb432ce315239d432cb8b68d508547100939ec0399"}, - {file = "watchfiles-0.17.0-pp39-pypy39_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a3d6c699f3ce238dfa90bcef501f331a69b0d9b076f14459ed8eab26ba2f4cf"}, - {file = "watchfiles-0.17.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f4271af86569bdbf131dd5c7c121c45d0ed194f3c88b88326e48a3b6a2db12"}, - {file = "watchfiles-0.17.0.tar.gz", hash = "sha256:ae7c57ef920589a40270d5ef3216d693f4e6f8864d8fc8b6cb7885ca98ad2a61"}, -] -websockets = [ - {file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"}, - {file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"}, - {file = "websockets-10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b"}, - {file = "websockets-10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c"}, - {file = "websockets-10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8"}, - {file = "websockets-10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677"}, - {file = "websockets-10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e"}, - {file = "websockets-10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f"}, - {file = "websockets-10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47"}, - {file = "websockets-10.3-cp310-cp310-win32.whl", hash = "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae"}, - {file = "websockets-10.3-cp310-cp310-win_amd64.whl", hash = "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079"}, - {file = "websockets-10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916"}, - {file = "websockets-10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb"}, - {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79"}, - {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d"}, - {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98"}, - {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e"}, - {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6"}, - {file = "websockets-10.3-cp37-cp37m-win32.whl", hash = "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1"}, - {file = "websockets-10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4"}, - {file = "websockets-10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36"}, - {file = "websockets-10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69"}, - {file = "websockets-10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd"}, - {file = "websockets-10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2"}, - {file = "websockets-10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c"}, - {file = "websockets-10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e"}, - {file = "websockets-10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991"}, - {file = "websockets-10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442"}, - {file = "websockets-10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76"}, - {file = "websockets-10.3-cp38-cp38-win32.whl", hash = "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559"}, - {file = "websockets-10.3-cp38-cp38-win_amd64.whl", hash = "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d"}, - {file = "websockets-10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094"}, - {file = "websockets-10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667"}, - {file = "websockets-10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731"}, - {file = "websockets-10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9"}, - {file = "websockets-10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680"}, - {file = "websockets-10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247"}, - {file = "websockets-10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af"}, - {file = "websockets-10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3"}, - {file = "websockets-10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8"}, - {file = "websockets-10.3-cp39-cp39-win32.whl", hash = "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582"}, - {file = "websockets-10.3-cp39-cp39-win_amd64.whl", hash = "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02"}, - {file = "websockets-10.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7"}, - {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f"}, - {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4"}, - {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755"}, - {file = "websockets-10.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55"}, - {file = "websockets-10.3.tar.gz", hash = "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"}, -] win32-setctime = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, @@ -1290,3 +1628,7 @@ wrapt = [ {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] +wsproto = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] diff --git a/pyproject.toml b/pyproject.toml index 34f32a4..1ef9d4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,35 +8,27 @@ license = "AGPLv3" [tool.poetry.dependencies] python = "^3.10" fastapi = ">=0.85.0" -uvicorn = {version = ">=0.18.3", extras = ["standard"]} httpx = {version = ">=0.23.0", extras = ["http2"]} httpx-cache = ">=0.6.0" toml = ">=0.10.2" slowapi = ">=0.1.6" orjson = ">=3.8.0" fastapi-cache2 = ">=0.1.9" -aioredis = {version = ">=2.0.1", extras = ["hiredis"]} redis = ">=4.3.4" -msgpack = ">=1.0.4" loguru = ">=0.6.0" sentry-sdk = ">=1.9.8" +argon2-cffi = ">=21.3.0" +hypercorn = {extras = ["uvloop"], version = ">=0.14.3"} +cytoolz = ">=0.12.0" +fastapi-paseto-auth = "^0.6.0" +ujson = ">=5.5.0" +hiredis = ">=2.0.0" +aiofiles = ">=22.1.0" [tool.poetry.dev-dependencies] -fastapi = ">=0.85.0" -uvicorn = {version = ">=0.18.3", extras = ["standard"]} -httpx = {version = ">=0.23.0", extras = ["http2"]} -httpx-cache = ">=0.6.0" -toml = ">=0.10.2" -slowapi = ">=0.1.6" -orjson = ">=3.8.0" -fastapi-cache2 = ">=0.1.9" -aioredis = {version = ">=2.0.1", extras = ["hiredis"]} -redis = ">=4.3.4" -msgpack = ">=1.0.4" mypy = ">=0.971" types-toml = ">=0.10.8" -loguru = ">=0.6.0" -sentry-sdk = ">=1.9.8" +types-redis = ">=4.3.21.1" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/requirements.txt b/requirements.txt index 3e41fe1..d16620f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,50 +1,61 @@ -aioredis==2.0.1; python_version >= "3.6" +aiofiles==22.1.0; python_version >= "3.7" and python_version < "4.0" aiorwlock==1.3.0; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.7.0" anyio==3.6.1; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.6.2" +argon2-cffi-bindings==21.2.0; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.10" +argon2-cffi==21.3.0; python_version >= "3.6" async-timeout==4.0.2; python_version >= "3.6" attrs==21.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.5.0" certifi==2022.9.24; python_version >= "3.7" and python_version < "4.0" +cffi==1.15.1; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.10" click==8.1.3; python_version >= "3.7" and python_version < "4.0" colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and python_version < "4.0" and platform_system == "Windows" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0" and python_version < "4.0" and platform_system == "Windows" +cryptography==37.0.4; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.10" +cytoolz==0.12.0; python_version >= "3.5" deprecated==1.2.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" fastapi-cache2==0.1.9; python_version >= "3.7" and python_version < "4.0" +fastapi-paseto-auth==0.6.0; python_version >= "3.10" fastapi==0.85.0; python_version >= "3.7" fasteners==0.17.3; python_version >= "3.7" and python_version < "4.0" -h11==0.12.0; python_version >= "3.7" and python_version < "4.0" +h11==0.12.0; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.7.0" h2==4.1.0; python_version >= "3.7" and python_full_version >= "3.6.1" and python_version < "4.0" -hiredis==2.0.0; implementation_name == "cpython" and python_version >= "3.6" -hpack==4.0.0; python_version >= "3.7" and python_full_version >= "3.6.1" and python_version < "4.0" +hiredis==2.0.0; python_version >= "3.6" +hpack==4.0.0; python_version >= "3.7" and python_full_version >= "3.6.1" httpcore==0.15.0; python_version >= "3.7" and python_version < "4.0" -httptools==0.5.0; python_version >= "3.7" and python_full_version >= "3.5.0" and python_version < "4.0" httpx-cache==0.6.0; python_version >= "3.7" and python_version < "4.0" httpx==0.23.0; python_version >= "3.7" -hyperframe==6.0.1; python_version >= "3.7" and python_full_version >= "3.6.1" and python_version < "4.0" -idna==3.4 +hypercorn==0.14.3; python_version >= "3.7" +hyperframe==6.0.1; python_version >= "3.7" and python_full_version >= "3.6.1" +idna==3.4; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.6.2" +iso8601==1.1.0; python_full_version >= "3.6.2" and python_version < "4.0" and python_full_version < "4.0.0" and python_version >= "3.10" limits==1.6; python_version >= "3.7" and python_version < "4.0" loguru==0.6.0; python_version >= "3.5" -msgpack==1.0.4 +msgpack==1.0.4; python_version >= "3.7" and python_version < "4.0" orjson==3.8.0; python_version >= "3.7" packaging==21.3; python_version >= "3.6" +passlib==1.7.4; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.10" pendulum==2.1.2; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.5.0" -pydantic==1.10.2; python_version >= "3.7" and python_version < "4.0" +priority==2.0.0; python_full_version >= "3.6.1" and python_version >= "3.7" +pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +pycryptodomex==3.15.0; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.10" +pydantic==1.10.2; python_version >= "3.10" and python_version < "4.0" pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.6" +pyseto==1.6.10; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.10" python-dateutil==2.8.2; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.5.0" -python-dotenv==0.21.0; python_version >= "3.7" and python_version < "4.0" pytzdata==2020.1; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.5.0" -pyyaml==6.0; python_version >= "3.7" and python_version < "4.0" redis==4.3.4; python_version >= "3.6" rfc3986==1.5.0; python_version >= "3.7" and python_version < "4.0" -sentry-sdk==1.9.9 +sentry-sdk==1.9.10 six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.5.0" slowapi==0.1.6; python_version >= "3.7" and python_version < "4.0" sniffio==1.3.0; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.6.2" -starlette==0.20.4; python_version >= "3.7" and python_version < "4.0" +starlette==0.20.4; python_version >= "3.10" and python_version < "4.0" toml==0.10.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") -typing-extensions==4.3.0; python_version >= "3.7" and python_version < "4.0" +toolz==0.12.0; python_version >= "3.5" +typing-extensions==4.4.0; python_version >= "3.10" +ujson==5.5.0; python_version >= "3.7" urllib3==1.26.12; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6" -uvicorn==0.18.3; python_version >= "3.7" -uvloop==0.17.0; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.7" and python_version < "4.0" -watchfiles==0.17.0; python_version >= "3.7" and python_version < "4.0" -websockets==10.3; python_version >= "3.7" and python_version < "4.0" +uvicorn==0.18.3; python_version >= "3.7" and python_version < "4.0" +uvloop==0.17.0; platform_system != "Windows" and python_version >= "3.7" win32-setctime==1.1.0; sys_platform == "win32" and python_version >= "3.5" wrapt==1.14.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +wsproto==1.2.0; python_full_version >= "3.7.0" and python_version >= "3.7" diff --git a/run.sh b/run.sh index 0e983be..f86523f 100755 --- a/run.sh +++ b/run.sh @@ -7,6 +7,6 @@ CORES=$(grep -c ^processor /proc/cpuinfo) # Start the application -uvicorn main:app --host="$UVICORN_HOST" --port="$UVICORN_PORT" \ ---workers="$CORES" --log-level="$UVICORN_LOG_LEVEL" --server-header \ ---proxy-headers --forwarded-allow-ips="*" \ No newline at end of file +hypercorn main:app --bind="${HYPERCORN_HOST}:${HYPERCORN_PORT}" \ +--workers="$CORES" --log-level="$HYPERCORN_LOG_LEVEL" \ +--worker-class uvloop \ No newline at end of file diff --git a/modules/__init__.py b/src/__init__.py similarity index 100% rename from modules/__init__.py rename to src/__init__.py diff --git a/src/controllers/Announcements.py b/src/controllers/Announcements.py new file mode 100644 index 0000000..3e8d932 --- /dev/null +++ b/src/controllers/Announcements.py @@ -0,0 +1,102 @@ +import toml +from redis import asyncio as aioredis + +import src.utils.Logger as Logger +from src.utils.Generators import Generators +from src.models.AnnouncementModels import AnnouncementCreateModel +from src.utils.RedisConnector import RedisConnector + +config: dict = toml.load("config.toml") + +class Announcements: + """Implements the announcements class for the ReVanced API""" + + redis = RedisConnector.connect(config['announcements']['database']) + + AnnouncementsLogger = Logger.AnnouncementsLogger() + + generators = Generators() + + async def store(self, announcement: AnnouncementCreateModel, author: str) -> bool: + """Store an announcement in the database + + Args: + announcement (AnnouncementCreateModel): Pydantic model of the announcement + + Returns: + str | bool: UUID of the announcement or False if the announcement wasn't stored successfully + """ + + announcement_id: str = "announcement" + + timestamp = await self.generators.generate_timestamp() + + announcement_payload: dict[str, str | int] = {} + + announcement_payload['created_at'] = timestamp + announcement_payload['author'] = author + announcement_payload['type'] = announcement.type + announcement_payload['title'] = announcement.title + announcement_payload['content'] = announcement.content + + try: + await self.redis.json().set(announcement_id, '$', announcement_payload) + await self.AnnouncementsLogger.log("SET", None, announcement_id) + except aioredis.RedisError as e: + await self.AnnouncementsLogger.log("SET", e) + raise e + + return True + + async def exists(self) -> bool: + """Check if an announcement exists in the database + + Returns: + bool: True if the announcement exists, False otherwise + """ + try: + if await self.redis.exists("announcement"): + await self.AnnouncementsLogger.log("EXISTS", None, "announcement") + return True + else: + await self.AnnouncementsLogger.log("EXISTS", None, "announcement") + return False + except aioredis.RedisError as e: + await self.AnnouncementsLogger.log("EXISTS", e) + raise e + + async def get(self) -> dict: + """Get a announcement from the database + + Returns: + dict: Dict of the announcement or an empty dict if the announcement doesn't exist + """ + + if await self.exists(): + try: + announcement: dict[str, str | int] = await self.redis.json().get("announcement") + await self.AnnouncementsLogger.log("GET", None, "announcement") + except aioredis.RedisError as e: + await self.AnnouncementsLogger.log("GET", e) + return {} + return announcement + else: + return {} + + async def delete(self) -> bool: + """Delete an announcement from the database + + Returns: + bool: True if the announcement was deleted successfully, False otherwise + """ + + if await self.exists(): + try: + await self.redis.delete("announcement") + await self.AnnouncementsLogger.log("DELETE", None, "announcement") + except aioredis.RedisError as e: + await self.AnnouncementsLogger.log("DELETE", e) + return False + return True + else: + return False \ No newline at end of file diff --git a/src/controllers/Auth.py b/src/controllers/Auth.py new file mode 100644 index 0000000..1a28540 --- /dev/null +++ b/src/controllers/Auth.py @@ -0,0 +1,7 @@ +import os +from pydantic import BaseModel + +class PasetoSettings(BaseModel): + authpaseto_secret_key: str = os.environ['SECRET_KEY'] + authpaseto_access_token_expires: int = 86400 + \ No newline at end of file diff --git a/src/controllers/Clients.py b/src/controllers/Clients.py new file mode 100644 index 0000000..8711c60 --- /dev/null +++ b/src/controllers/Clients.py @@ -0,0 +1,351 @@ +from time import sleep +import toml +import orjson +from typing import Optional +import argon2 +from redis import asyncio as aioredis +import aiofiles +import uvloop + +import src.utils.Logger as Logger +from src.utils.Generators import Generators +from src.models.ClientModels import ClientModel +from src.utils.RedisConnector import RedisConnector + +config: dict = toml.load("config.toml") + +class Clients: + + """Implements a client for ReVanced Releases API.""" + + uvloop.install() + + redis = RedisConnector.connect(config['clients']['database']) + redis_tokens = RedisConnector.connect(config['tokens']['database']) + + UserLogger = Logger.UserLogger() + + generators = Generators() + + async def generate(self, admin: Optional[bool] = False) -> ClientModel: + """Generate a new client + + Args: + admin (Optional[bool], optional): Defines if the client should have admin access. Defaults to False. + + Returns: + ClientModel: Pydantic model of the client + """ + + client_id: str = await self.generators.generate_id() + client_secret: str = await self.generators.generate_secret() + + client = ClientModel(id=client_id, secret=client_secret, admin=admin, active=True) + + return client + + async def store(self, client: ClientModel) -> bool: + """Store a client in the database + + Args: + client (ClientModel): Pydantic model of the client + + Returns: + bool: True if the client was stored successfully, False otherwise + """ + + client_payload: dict[str, str | bool] = {} + ph: argon2.PasswordHasher = argon2.PasswordHasher() + + client_payload['secret'] = ph.hash(client.secret) + client_payload['admin'] = client.admin + client_payload['active'] = client.active + + try: + await self.redis.json().set(client.id, '$', client_payload) + await self.UserLogger.log("SET", None, client.id) + except aioredis.RedisError as e: + await self.UserLogger.log("SET", e) + raise e + + return True + + async def exists(self, client_id: str) -> bool: + """Check if a client exists in the database + + Args: + client_id (str): UUID of the client + + Returns: + bool: True if the client exists, False otherwise + """ + try: + if await self.redis.exists(client_id): + await self.UserLogger.log("EXISTS", None, client_id) + return True + else: + await self.UserLogger.log("EXISTS", None, client_id) + return False + except aioredis.RedisError as e: + await self.UserLogger.log("EXISTS", e) + raise e + + async def get(self, client_id: str) -> ClientModel | bool: + """Get a client from the database + + Args: + client_id (str): UUID of the client + + Returns: + ClientModel | bool: Pydantic model of the client or False if the client doesn't exist + """ + + if await self.exists(client_id): + try: + client_payload: dict[str, str | bool] = await self.redis.json().get(client_id) + client = ClientModel(id=client_id, secret=client_payload['secret'], admin=client_payload['admin'], active=True) + await self.UserLogger.log("GET", None, client_id) + except aioredis.RedisError as e: + await self.UserLogger.log("GET", e) + raise e + return client + else: + return False + + async def delete(self, client_id: str) -> bool: + """Delete a client from the database + + Args: + client_id (str): UUID of the client + + Returns: + bool: True if the client was deleted successfully, False otherwise + """ + + if await self.exists(client_id): + try: + await self.redis.delete(client_id) + await self.UserLogger.log("DELETE", None, client_id) + except aioredis.RedisError as e: + await self.UserLogger.log("DELETE", e) + raise e + return True + else: + return False + + async def update_secret(self, client_id: str, new_secret: str) -> bool: + """Update the secret of a client + + Args: + client_id (str): UUID of the client + new_secret (str): New secret of the client + + Returns: + bool: True if the secret was updated successfully, False otherwise + """ + + ph: argon2.PasswordHasher = argon2.PasswordHasher() + + updated: bool = False + + try: + await self.redis.json().set(client_id, '.secret', ph.hash(new_secret)) + await self.UserLogger.log("UPDATE_SECRET", None, client_id) + updated = True + except aioredis.RedisError as e: + await self.UserLogger.log("UPDATE_SECRET", e) + raise e + + return updated + + async def authenticate(self, client_id: str, secret: str) -> bool: + """Check if the secret of a client is correct + + Args: + client_id (str): UUID of the client + secret (str): Secret of the client + + Returns: + bool: True if the secret is correct, False otherwise + """ + + ph: argon2.PasswordHasher = argon2.PasswordHasher() + authenticated: bool = False + client_secret: str = await self.redis.json().get(client_id, '.secret') + + try: + if ph.verify(client_secret, secret): + await self.UserLogger.log("CHECK_SECRET", None, client_id) + + if ph.check_needs_rehash(client_secret): + await self.redis.json().set(client_id, '.secret', ph.hash(secret)) + await self.UserLogger.log("REHASH SECRET", None, client_id) + authenticated = True + except argon2.exceptions.VerifyMismatchError as e: + await self.UserLogger.log("CHECK_SECRET", e) + return authenticated + + return authenticated + + async def is_admin(self, client_id: str) -> bool: + """Check if a client has admin access + + Args: + client_id (str): UUID of the client + + Returns: + bool: True if the client has admin access, False otherwise + """ + + client_admin: bool = False + + try: + client_admin = await self.redis.json().get(client_id, '.admin') + await self.UserLogger.log("CHECK_ADMIN", None, client_id) + except aioredis.RedisError as e: + await self.UserLogger.log("CHECK_ADMIN", e) + raise e + + return client_admin + + + async def is_active(self, client_id: str) -> bool: + """Check if a client is active + + Args: + client_id (str): UUID of the client + + Returns: + bool: True if the client is active, False otherwise + """ + + client_active: bool = False + + try: + client_active = await self.redis.json().get(client_id, '.active') + await self.UserLogger.log("CHECK_ACTIVE", None, client_id) + except aioredis.RedisError as e: + await self.UserLogger.log("CHECK_ACTIVE", e) + raise e + + return client_active + + async def status(self, client_id: str, active: bool) -> bool: + """Activate a client + + Args: + client_id (str): UUID of the client + active (bool): True to activate the client, False to deactivate it + + Returns: + bool: True if the client status was change successfully, False otherwise + """ + + changed: bool = False + + try: + await self.redis.json().set(client_id, '.active', active) + await self.UserLogger.log("ACTIVATE", None, client_id) + changed = True + except aioredis.RedisError as e: + await self.UserLogger.log("ACTIVATE", e) + raise e + + return changed + + async def ban_token(self, token: str) -> bool: + """Ban a token + + Args: + token (str): Token to ban + + Returns: + bool: True if the token was banned successfully, False otherwise + """ + + banned: bool = False + + try: + await self.redis_tokens.set(token, '') + await self.UserLogger.log("BAN_TOKEN", None, token) + banned = True + except aioredis.RedisError as e: + await self.UserLogger.log("BAN_TOKEN", e) + raise e + + return banned + + async def is_token_banned(self, token: str) -> bool: + """Check if a token is banned + + Args: + token (str): Token to check + + Returns: + bool: True if the token is banned, False otherwise + """ + + banned: bool = True + + try: + banned = await self.redis_tokens.exists(token) + await self.UserLogger.log("CHECK_TOKEN", None, token) + except aioredis.RedisError as e: + await self.UserLogger.log("CHECK_TOKEN", e) + raise e + + return banned + + async def auth_checks(self, client_id: str, token: str) -> bool: + """Check if a client exists, is active and the token isn't banned + + Args: + client_id (str): UUID of the client + secret (str): Secret of the client + + Returns: + bool: True if the client exists, is active + and the token isn't banned, False otherwise + """ + + if await self.exists(client_id): + if await self.is_active(client_id): + if not await self.is_token_banned(token): + return True + else: + return False + else: + if not await self.is_token_banned(token): + await self.ban_token(token) + return False + else: + await self.ban_token(token) + return False + + return False + + async def setup_admin(self) -> bool: + """Create the admin user if it doesn't exist + + Returns: + bool: True if the admin user was created successfully, False otherwise + """ + created: bool = False + + if not await self.exists('admin'): + admin_info: ClientModel = await self.generate() + admin_info.id = 'admin' + admin_info.admin = True + try: + await self.store(admin_info) + await self.UserLogger.log("CREATE_ADMIN | ID |", None, admin_info.id) + await self.UserLogger.log("CREATE_ADMIN | SECRET |", None, admin_info.secret) + async with aiofiles.open("admin_info.json", "wb") as file: + await file.write(orjson.dumps(vars(admin_info))) + await self.UserLogger.log("CREATE_ADMIN | TO FILE", None, "admin_info.json") + created = True + except aioredis.RedisError as e: + await self.UserLogger.log("CREATE_ADMIN", e) + raise e + + return created \ No newline at end of file diff --git a/modules/Releases.py b/src/controllers/Releases.py similarity index 55% rename from modules/Releases.py rename to src/controllers/Releases.py index 697c57c..d48ea8e 100644 --- a/modules/Releases.py +++ b/src/controllers/Releases.py @@ -1,33 +1,23 @@ -import os +from toolz.dicttoolz import keyfilter +import asyncio +import uvloop import orjson -import httpx_cache from base64 import b64decode -from modules.utils.InternalCache import InternalCache -import modules.utils.Logger as Logger +from src.utils.HTTPXClient import HTTPXClient +from src.utils.InternalCache import InternalCache class Releases: """Implements the methods required to get the latest releases and patches from revanced repositories.""" - - headers = {'Accept': "application/vnd.github+json", - 'Authorization': "token " + os.environ['GITHUB_TOKEN'] - } - httpx_logger = Logger.HTTPXLogger() - - httpx_client = httpx_cache.AsyncClient( - headers=headers, - http2=True, - event_hooks={ - 'request': [httpx_logger.log_request], - 'response': [httpx_logger.log_response] - } - ) + uvloop.install() + + httpx_client = HTTPXClient.create() InternalCache = InternalCache() - async def _get_release(self, repository: str) -> list: + async def __get_release(self, repository: str) -> list: # Get assets from latest release in a given repository. # # Args: @@ -83,16 +73,17 @@ class Releases: releases = {} releases['tools'] = [] - for repository in repositories: - files = await self._get_release(repository) - if files: - for file in files: - releases['tools'].append(file) + results: list = await asyncio.gather(*[self.__get_release(repository) for repository in repositories]) + + for result in results: + for asset in result: + releases['tools'].append(asset) + await self.InternalCache.store('releases', releases) return releases - async def _get_patches_json(self) -> dict: + async def __get_patches_json(self) -> dict: # Get revanced-patches repository's README.md. # # Returns: @@ -113,12 +104,12 @@ class Releases: if await self.InternalCache.exists('patches'): patches = await self.InternalCache.get('patches') else: - patches = await self._get_patches_json() + patches = await self.__get_patches_json() await self.InternalCache.store('patches', patches) return patches - async def _get_contributors(self, repository: str) -> list: + async def __get_contributors(self, repository: str) -> list: # Get contributors from a given repository. # # Args: @@ -127,9 +118,14 @@ class Releases: # Returns: # list: a list of dictionaries containing the repository's contributors + keep: set = {'login', 'avatar_url', 'html_url'} + response = await self.httpx_client.get(f"https://api.github.com/repos/{repository}/contributors") - return response.json() + contributors: list = [keyfilter(lambda k: k in keep, contributor) for contributor in response.json()] + + + return contributors async def get_contributors(self, repositories: list) -> dict: """Runs get_contributors() asynchronously for each repository. @@ -148,11 +144,72 @@ class Releases: else: contributors = {} contributors['repositories'] = [] - for repository in repositories: - if 'revanced' in repository: - repo_contributors = await self._get_contributors(repository) - data = { 'name': repository, 'contributors': repo_contributors } - contributors['repositories'].append(data) + + revanced_repositories = [repository for repository in repositories if 'revanced' in repository] + + results: list[dict] = await asyncio.gather(*[self.__get_contributors(repository) for repository in revanced_repositories]) + + for key, value in zip(revanced_repositories, results): + data = { 'name': key, 'contributors': value } + contributors['repositories'].append(data) + await self.InternalCache.store('contributors', contributors) - return contributors \ No newline at end of file + return contributors + + async def get_commits(self, org: str, repository: str, path: str) -> dict: + """Get commit history from a given repository. + + Args: + org (str): Username of the organization | valid values: revanced or vancedapp + repository (str): Repository name + path (str): Path to the file + per_page (int): Number of commits to return + since (str): ISO 8601 timestamp + + Raises: + Exception: Raise a generic exception if the organization is not revanced or vancedapp + + Returns: + dict: a dictionary containing the repository's latest commits + """ + + payload: dict = {} + payload["repository"] = f"{org}/{repository}" + payload["path"] = path + payload["commits"] = [] + + if org == 'revanced' or org == 'vancedapp': + key: str = f"{org}/{repository}/{path}" + if await self.InternalCache.exists(key): + return await self.InternalCache.get(key) + else: + + _releases = await self.httpx_client.get( + f"https://api.github.com/repos/{org}/{repository}/releases?per_page=2" + ) + + releases = _releases.json() + + since = releases[1]['created_at'] + until = releases[0]['created_at'] + + _response = await self.httpx_client.get( + f"https://api.github.com/repos/{org}/{repository}/commits?path={path}&since={since}&until={until}" + ) + + response = _response.json() + + for commit in response: + data: dict[str, str] = {} + data["sha"] = commit["sha"] + data["author"] = commit["commit"]["author"]["name"] + data["message"] = commit["commit"]["message"] + data["html_url"] = commit["html_url"] + payload['commits'].append(data) + + await self.InternalCache.store(key, payload) + + return payload + else: + raise Exception("Invalid organization.") \ No newline at end of file diff --git a/modules/models/__init__.py b/src/controllers/__init__.py similarity index 100% rename from modules/models/__init__.py rename to src/controllers/__init__.py diff --git a/src/models/AnnouncementModels.py b/src/models/AnnouncementModels.py new file mode 100644 index 0000000..1742ad0 --- /dev/null +++ b/src/models/AnnouncementModels.py @@ -0,0 +1,46 @@ +from pydantic import BaseModel +from typing import Literal + +AnnouncementType = Literal["info", "warning", "error"] + +class AnnouncementModel(BaseModel): + """Implements the fields for the announcements. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + created_at: int + author: str + type: AnnouncementType + title: str + content: str + +class AnnouncementCreateModel(BaseModel): + """Implements the fields for creating an announcement. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + type: AnnouncementType + title: str + content: str + +class AnnouncementCreatedResponse(BaseModel): + """Implements the response fields for created announcements. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + created: bool + +class AnnouncementDeleted(BaseModel): + """Implements the response fields for deleted announcements. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + deleted: bool \ No newline at end of file diff --git a/src/models/ClientModels.py b/src/models/ClientModels.py new file mode 100644 index 0000000..67188fb --- /dev/null +++ b/src/models/ClientModels.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel + +class ClientModel(BaseModel): + """Implements the fields for the clients. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + id: str + secret: str + admin: bool + active: bool + +class ClientAuthModel(BaseModel): + """Implements the fields for client authentication. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + id: str + secret: str + diff --git a/src/models/GeneralErrors.py b/src/models/GeneralErrors.py new file mode 100644 index 0000000..97c0879 --- /dev/null +++ b/src/models/GeneralErrors.py @@ -0,0 +1,51 @@ +from pydantic import BaseModel + +class InternalServerError(BaseModel): + """Implements the response fields for when an internal server error occurs. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + error: str = "Internal Server Error" + message: str = "An internal server error occurred. Please try again later." + +class AnnouncementNotFound(BaseModel): + """Implements the response fields for when an item is not found. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + error: str = "Not Found" + message: str = "No announcement was found." + +class ClientNotFound(BaseModel): + """Implements the response fields for when a client is not found. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + error: str = "Not Found" + message: str = "No client matches the given ID" + +class IdNotProvided(BaseModel): + """Implements the response fields for when the id is not provided. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + error: str = "Bad Request" + message: str = "Missing client id" + +class Unauthorized(BaseModel): + """Implements the response fields for when the client is unauthorized. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + error: str = "Unauthorized" + message: str = "The client is unauthorized to access this resource" \ No newline at end of file diff --git a/modules/models/ResponseFields.py b/src/models/ResponseFields.py similarity index 82% rename from modules/models/ResponseFields.py rename to src/models/ResponseFields.py index e18db1d..5de8f38 100644 --- a/modules/models/ResponseFields.py +++ b/src/models/ResponseFields.py @@ -52,24 +52,8 @@ class ContributorFields(BaseModel): BaseModel (pydantic.BaseModel): BaseModel from pydantic """ login: str - id: str - node_id: str avatar_url: str - gravatar_id: str - url: str html_url: str - followers_url: str - following_url: str - gists_url: str - starred_url: str - subscriptions_url: str - organizations_url: str - repos_url: str - events_url: str - received_events_url: str - type: str - site_admin: str - contributions: int class ContributorsResponseFields(BaseModel): """Implements the fields for each repository in the /contributors endpoint @@ -79,4 +63,15 @@ class ContributorsResponseFields(BaseModel): """ name: str - contributors: list[ ContributorFields ] \ No newline at end of file + contributors: list[ ContributorFields ] + +class ChangelogsResponseFields(BaseModel): + """Implements the fields for the /changelogs endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + sha: str + author: str + message: str + html_url: str \ No newline at end of file diff --git a/src/models/ResponseModels.py b/src/models/ResponseModels.py new file mode 100644 index 0000000..70b9976 --- /dev/null +++ b/src/models/ResponseModels.py @@ -0,0 +1,101 @@ +from pydantic import BaseModel +import src.models.ResponseFields as ResponseFields + +"""Implements pydantic models and model generator for the API's responses.""" + +class ToolsResponseModel(BaseModel): + """Implements the JSON response model for the /tools endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + tools: list[ ResponseFields.ToolsResponseFields ] + +class PatchesResponseModel(BaseModel): + """Implements the JSON response model for the /patches endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + __root__: list[ ResponseFields.PatchesResponseFields ] + +class ContributorsResponseModel(BaseModel): + """Implements the JSON response model for the /contributors endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + repositories: list[ ResponseFields.ContributorsResponseFields ] + +class PingResponseModel(BaseModel): + """Implements the JSON response model for the /heartbeat endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + status: int + detail: str + +class ClientDeletedResponse(BaseModel): + """Implements the response fields for deleted clients. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + id: str + deleted: bool + +class ClientSecretUpdatedResponse(BaseModel): + """Implements the response fields for updated client secrets. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + id: str + secret: str + +class ClientAuthTokenResponse(BaseModel): + """Implements the response fields for client auth tokens. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + access_token: str + refresh_token: str + +class ClientTokenRefreshResponse(BaseModel): + """Implements the response fields for client token refresh. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + access_token: str + +class ClientStatusResponse(BaseModel): + """Implements the response fields for client status. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + id: str + active: bool + +class ChangelogsResponseModel(BaseModel): + """Implements the JSON response model for the /changelogs endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + repository: str + path: str + commits: list[ ResponseFields.ChangelogsResponseFields ] \ No newline at end of file diff --git a/modules/utils/__init__.py b/src/models/__init__.py similarity index 100% rename from modules/utils/__init__.py rename to src/models/__init__.py diff --git a/src/utils/Generators.py b/src/utils/Generators.py new file mode 100644 index 0000000..95c3d69 --- /dev/null +++ b/src/utils/Generators.py @@ -0,0 +1,30 @@ +import time +import uuid +import secrets + +class Generators: + """Generates UUIDs and secrets""" + + async def generate_secret(self) -> str: + """Generate a random secret + + Returns: + str: A random secret + """ + return secrets.token_urlsafe(32) + + async def generate_id(self) -> str: + """Generate a random UUID + + Returns: + str: A random UUID (str instead of UUID object) + """ + return str(uuid.uuid4()) + + async def generate_timestamp(self) -> int: + """Generate a timestamp + + Returns: + int: A timestamp + """ + return int(time.time()) \ No newline at end of file diff --git a/src/utils/HTTPXClient.py b/src/utils/HTTPXClient.py new file mode 100644 index 0000000..1d5ef4b --- /dev/null +++ b/src/utils/HTTPXClient.py @@ -0,0 +1,32 @@ +import os +import httpx_cache +import src.utils.Logger as Logger + +class HTTPXClient: + + """Implements the methods required to get the latest releases and patches from revanced repositories.""" + + @staticmethod + def create() -> httpx_cache.AsyncClient: + """Create HTTPX client with cache + + Returns: + httpx_cache.AsyncClient: HTTPX client with cache + """ + + headers = {'Accept': "application/vnd.github+json", + 'Authorization': "token " + os.environ['GITHUB_TOKEN'] + } + + httpx_logger = Logger.HTTPXLogger() + + httpx_client = httpx_cache.AsyncClient( + headers=headers, + http2=True, + event_hooks={ + 'request': [httpx_logger.log_request], + 'response': [httpx_logger.log_response] + } + ) + + return httpx_client \ No newline at end of file diff --git a/modules/utils/InternalCache.py b/src/utils/InternalCache.py similarity index 55% rename from modules/utils/InternalCache.py rename to src/utils/InternalCache.py index 942a8d9..fa3be43 100644 --- a/modules/utils/InternalCache.py +++ b/src/utils/InternalCache.py @@ -1,55 +1,57 @@ import os import toml -import orjson -import msgpack -import aioredis +from typing import Any +from redis import asyncio as aioredis -import modules.utils.Logger as Logger - -# Load config +import src.utils.Logger as Logger +from src.utils.RedisConnector import RedisConnector config: dict = toml.load("config.toml") -# Redis connection parameters - -redis_config: dict[ str, str | int ] = { - "url": f"redis://{os.environ['REDIS_URL']}", - "port": os.environ['REDIS_PORT'], - "database": config['internal-cache']['database'], -} - class InternalCache: """Implements an internal cache for ReVanced Releases API.""" - redis_url = f"{redis_config['url']}:{redis_config['port']}/{redis_config['database']}" - redis = aioredis.from_url(redis_url, encoding="utf-8", decode_responses=True) + redis = RedisConnector.connect(config['internal-cache']['database']) InternalCacheLogger = Logger.InternalCacheLogger() async def store(self, key: str, value: dict) -> None: + """Stores a key-value pair in the cache. + + Args: + key (str): the key to store + value (dict): the JSON value to store + """ try: - await self.redis.set(key, orjson.dumps(value), ex=config['internal-cache']['expire']) + await self.redis.json().set(key, '$', value) + await self.redis.expire(key, config['internal-cache']['expire']) await self.InternalCacheLogger.log("SET", None, key) except aioredis.RedisError as e: await self.InternalCacheLogger.log("SET", e) async def delete(self, key: str) -> None: + """Removes a key-value pair from the cache. + + Args: + key (str): the key to delete + """ try: await self.redis.delete(key) await self.InternalCacheLogger.log("DEL", None, key) except aioredis.RedisError as e: await self.InternalCacheLogger.log("DEL", e) - async def update(self, key: str, value: dict) -> None: - try: - await self.redis.set(key, orjson.dumps(value), ex=config['internal-cache']['expire']) - await self.InternalCacheLogger.log("SET", None, key) - except aioredis.RedisError as e: - await self.InternalCacheLogger.log("SET", e) - async def get(self, key: str) -> dict: + """Gets a key-value pair from the cache. + + Args: + key (str): the key to retrieve + + Returns: + dict: the JSON value stored in the cache or an empty dict if key doesn't exist or an error occurred + """ try: - payload = orjson.loads(await self.redis.get(key)) + payload: dict[Any, Any] = await self.redis.json().get(key) await self.InternalCacheLogger.log("GET", None, key) return payload except aioredis.RedisError as e: @@ -57,6 +59,14 @@ class InternalCache: return {} async def exists(self, key: str) -> bool: + """Checks if a key exists in the cache. + + Args: + key (str): key to check + + Returns: + bool: True if key exists, False if key doesn't exist or an error occurred + """ try: if await self.redis.exists(key): await self.InternalCacheLogger.log("EXISTS", None, key) diff --git a/modules/utils/Logger.py b/src/utils/Logger.py similarity index 64% rename from modules/utils/Logger.py rename to src/utils/Logger.py index 8d7ea3b..ae7bffe 100644 --- a/modules/utils/Logger.py +++ b/src/utils/Logger.py @@ -4,6 +4,7 @@ from loguru import logger from typing import Optional from types import FrameType from redis import RedisError +from argon2.exceptions import VerifyMismatchError class InterceptHandler(logging.Handler): """Setups a loging handler for uvicorn and FastAPI. @@ -24,15 +25,22 @@ class InterceptHandler(logging.Handler): depth: int # Get corresponding Loguru level if it exists + # If not, use default level + try: level = logger.level(record.levelname).name except ValueError: level = record.levelno + # Find caller from where originated the logged message + # Set depth to 2 to avoid logging of loguru internal calls frame = logging.currentframe() depth = 2 # Find caller from where originated the logged message + # The logging module uses a stack frame to keep track of where logging messages originate + # This stack frame is used to find the correct place in the code where the logging message was generated + # The mypy error is ignored because the logging module is not properly typed while frame.f_code.co_filename == logging.__file__: frame = frame.f_back depth += 1 @@ -74,6 +82,33 @@ class InternalCacheLogger: else: logger.info(f"[InternalCache] REDIS {operation} {key} - OK") +class UserLogger: + async def log(self, operation: str, result: RedisError | VerifyMismatchError | None = None, + key: str = "",) -> None: + """Logs internal cache operations + + Args: + operation (str): Operation name + key (str): Key used in the operation + """ + if type(result) is RedisError: + logger.error(f"[User] REDIS {operation} - Failed with error: {result}") + else: + logger.info(f"[User] REDIS {operation} {key} - OK") + +class AnnouncementsLogger: + async def log(self, operation: str, result: RedisError | None = None, key: str = "") -> None: + """Logs internal cache operations + + Args: + operation (str): Operation name + key (str): Key used in the operation + """ + if type(result) is RedisError: + logger.error(f"[User] REDIS {operation} - Failed with error: {result}") + else: + logger.info(f"[User] REDIS {operation} {key} - OK") + def setup_logging(LOG_LEVEL: str, JSON_LOGS: bool) -> None: """Setup logging for uvicorn and FastAPI.""" @@ -89,4 +124,4 @@ def setup_logging(LOG_LEVEL: str, JSON_LOGS: bool) -> None: logging.getLogger(name).propagate = True # configure loguru - logger.configure(handlers=[{"sink": sys.stdout, "serialize": JSON_LOGS}]) \ No newline at end of file + logger.configure(handlers=[{"sink": sys.stdout, "serialize": JSON_LOGS}]) diff --git a/src/utils/RedisConnector.py b/src/utils/RedisConnector.py new file mode 100644 index 0000000..e60c5b5 --- /dev/null +++ b/src/utils/RedisConnector.py @@ -0,0 +1,23 @@ +import os +import toml +from redis import asyncio as aioredis + +# Load config + +config: dict = toml.load("config.toml") + +# Redis connection parameters + +redis_config: dict[ str, str | int ] = { + "url": f"redis://{os.environ['REDIS_URL']}", + "port": os.environ['REDIS_PORT'], +} + +class RedisConnector: + """Implements the RedisConnector class for the ReVanced API""" + + @staticmethod + def connect(database: str) -> aioredis.Redis: + """Connect to Redis""" + redis_url = f"{redis_config['url']}:{redis_config['port']}/{database}" + return aioredis.from_url(redis_url, encoding="utf-8", decode_responses=True) \ No newline at end of file diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29