diff --git a/.dockerignore b/.dockerignore
index 0bd5f17..8a89262 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,8 +1,10 @@
-poetry.toml
.idea
.DS_Store
.github
.vscode
+venv
+.git
+psqlData
# Created by https://www.toptal.com/developers/gitignore/api/vscode,python
# Edit at https://www.toptal.com/developers/gitignore?templates=vscode,python
diff --git a/.gitignore b/.gitignore
index 0c6eda1..8866ce3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ ansible/
configs/
docker-compose.yml
run_*
+psqlData
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cd7c608..c5ad27f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,42 +1,40 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.0.1
+ rev: v4.5.0
hooks:
- id: check-yaml
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- - id: requirements-txt-fixer
- repo: https://github.com/pappasam/toml-sort.git
- rev: v0.19.0
+ rev: v0.23.1
hooks:
- id: toml-sort
args: [ "--in-place", "--all", "--ignore-case" ]
- repo: https://github.com/asottile/pyupgrade
- rev: v2.21.0
+ rev: v3.15.2
hooks:
- id: pyupgrade
- repo: https://github.com/hadialqattan/pycln
- rev: v0.0.4
+ rev: v2.4.0
hooks:
- id: pycln
- args: [--config=pyproject.toml]
-
+ args: [ --config=pyproject.toml ]
# - repo: https://github.com/PyCQA/bandit
- # rev: '1.7.0'
+ # rev: '1.7.8'
# hooks:
# - id: bandit
- repo: https://github.com/PyCQA/isort
- rev: 5.9.2
+ rev: 5.13.2
hooks:
- id: isort
args: [ "--profile", "black", "--filter-files", "--combine-as" ]
- repo: https://github.com/psf/black
- rev: 22.3.0
+ rev: 24.4.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
- rev: 3.9.2
+ rev: 7.0.0
hooks:
- id: flake8
# - repo: https://github.com/pre-commit/mirrors-mypy
diff --git a/Dockerfile b/Dockerfile
index 9f3dce3..936bbd8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
-FROM tiangolo/uvicorn-gunicorn:python3.8-slim
+FROM tiangolo/uvicorn-gunicorn:python3.10-slim
ENV HOME="/root"
WORKDIR /root
diff --git a/brick_server/minimal/__main__.py b/brick_server/minimal/__main__.py
index 5a8482a..3272c9b 100644
--- a/brick_server/minimal/__main__.py
+++ b/brick_server/minimal/__main__.py
@@ -1,45 +1,65 @@
import click
-from click_default_group import DefaultGroup
import uvicorn
-from fastapi_rest_framework import cli, config
+from click_default_group import DefaultGroup
-from brick_server.minimal.config import FastAPIConfig
+from brick_server.minimal.config.manager import settings
-@click.group(cls=DefaultGroup, default='serve', default_if_no_args=True)
+@click.group(cls=DefaultGroup, default="serve", default_if_no_args=True)
@click.help_option("--help", "-h")
def cli_group():
pass
-@cli.command()
+@click.command()
def serve() -> None:
- settings = config.init_settings(FastAPIConfig)
uvicorn.run(
- "brick_server.minimal.app:app",
- host=settings.host,
- port=settings.port,
- debug=settings.debug,
- reload=settings.debug,
- log_level="debug",
+ app="brick_server.minimal.app:backend_app",
+ host=settings.SERVER_HOST,
+ port=settings.SERVER_PORT,
+ reload=settings.SERVER_WORKERS == 1 and settings.DEBUG,
reload_dirs=["brick_server/minimal"],
- workers=settings.workers,
+ workers=settings.SERVER_WORKERS,
+ log_level=settings.LOGGING_LEVEL,
+ proxy_headers=True,
+ forwarded_allow_ips="*",
)
-@cli.command()
-@click.option("--user-id", type=str, default="admin")
-@click.option("--token-lifetime", type=int, default=0, help="use ")
-def generate_jwt(user_id: str, token_lifetime: int) -> None:
- settings = config.init_settings(FastAPIConfig)
+# @click.command("openapi")
+# @click.option("-o", "--output", type=click.Path(), required=False, default=None)
+# def main(output: Optional[str]) -> None:
+# openapi_json = json.dumps(app.openapi(), indent=2)
+# if output is None:
+# print(openapi_json)
+# else:
+# with Path(output).open("w", encoding="utf-8") as f:
+# f.write(openapi_json)
- from brick_server.minimal.auth.authorization import create_jwt_token
+@click.command()
+@click.option("--user-id", type=str, default="admin")
+@click.option("--app-name", type=str, default="")
+@click.option("--domain", type=str, default="")
+@click.option("--token-lifetime", type=int, default=0)
+@click.option("--create-user", is_flag=True)
+def generate_jwt(
+ user_id: str, app_name: str, domain: str, token_lifetime: int, create_user: bool
+) -> None:
+ from brick_server.minimal.auth.authorization import create_user
+ from brick_server.minimal.auth.jwt import create_jwt_token
+ from brick_server.minimal.dbs import mongo_connection
+ from brick_server.minimal.models import User, get_doc_or_none
+
+ _ = mongo_connection # prevent import removed by pycln
if token_lifetime == 0:
- token_lifetime = settings.jwt_expire_seconds
+ token_lifetime = settings.JWT_EXPIRE_SECONDS
+ user = get_doc_or_none(User, user_id=user_id)
+ if create_user and user is None:
+ user = create_user(name=user_id, user_id=user_id, email=f"{user_id}@gmail.com")
jwt = create_jwt_token(
- user_id=user_id, app_name=None, token_lifetime=token_lifetime
+ user_id=user_id, app_name=app_name, domain=domain, token_lifetime=token_lifetime
)
print(jwt)
diff --git a/brick_server/minimal/app.py b/brick_server/minimal/app.py
index d30f0b4..07bad25 100644
--- a/brick_server/minimal/app.py
+++ b/brick_server/minimal/app.py
@@ -1,75 +1,53 @@
-import os
-
-import asyncpg
-from fastapi import FastAPI
+import fastapi
from fastapi.middleware.cors import CORSMiddleware
-from fastapi_rest_framework import config, logging
+from fastapi_restful.timing import add_timing_middleware
from loguru import logger
-from starlette.middleware.sessions import SessionMiddleware
-
-from brick_server.minimal.config import FastAPIConfig
-
-settings = config.init_settings(FastAPIConfig)
-API_V1_PREFIX = "/brickapi/v1"
-
-logging.init_logging()
-logging.intercept_all_loggers()
-app = FastAPI(title="Brick Server", openapi_url="/docs/openapi.json")
-
-origins = ["*"]
-app.add_middleware(
- CORSMiddleware,
- allow_origins=origins,
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
+from brick_server.minimal.config.errors import register_error_handlers
+from brick_server.minimal.config.events import (
+ execute_backend_server_event_handler,
+ terminate_backend_server_event_handler,
)
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.securities.auth import default_auth_logic
+from brick_server.minimal.utilities.dependencies import update_dependency_supplier
+from brick_server.minimal.utilities.logging import init_logging, intercept_all_loggers
+
+update_dependency_supplier(default_auth_logic)
-app.logger = logger
+def initialize_backend_application() -> fastapi.FastAPI:
+ init_logging()
+ intercept_all_loggers()
+ app = fastapi.FastAPI(**settings.set_backend_app_attributes) # type: ignore
-async def initialization() -> None:
- from brick_server.minimal.dbs import ts_db
+ add_timing_middleware(app, record=logger.info)
+ app.logger = logger
- # await graphdb.init_repository()
- # graphs = await graphdb.list_graphs()
- # if settings.default_brick_url in graphs:
- # logger.info("GraphDB Brick Schema found.")
- # else:
- # logger.info("GraphDB Brick Schema not found.")
- # await graphdb.import_schema_from_url(settings.default_brick_url)
- # logger.info("Brick SPARQL load schema")
- # await brick_sparql.load_schema()
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=settings.ALLOWED_ORIGINS,
+ allow_credentials=settings.IS_ALLOWED_CREDENTIALS,
+ allow_methods=settings.ALLOWED_METHODS,
+ allow_headers=settings.ALLOWED_HEADERS,
+ )
- try:
- logger.info("Init timescale tables")
- await ts_db.init()
- except asyncpg.exceptions.DuplicateTableError:
- logger.info("Timescale tables have been already created.")
+ app.add_event_handler(
+ "startup",
+ execute_backend_server_event_handler(backend_app=app),
+ )
+ app.add_event_handler(
+ "shutdown",
+ terminate_backend_server_event_handler(backend_app=app),
+ )
+ register_error_handlers(app)
-@app.on_event("startup")
-async def startup_event() -> None:
- await initialization()
+ from brick_server.minimal.services import router as api_endpoint_router
+ app.include_router(router=api_endpoint_router, prefix=settings.API_PREFIX)
-from .auth.auth_server import auth_router
-from .services.actuation import actuation_router
-from .services.data import data_router
-from .services.domain import domain_router
-from .services.entities import entity_router
-from .services.grafana import grafana_router
-from .services.queries import query_router
+ return app
-app.include_router(data_router, prefix="/brickapi/v1/data")
-app.include_router(domain_router, prefix="/brickapi/v1/domains")
-app.include_router(entity_router, prefix="/brickapi/v1/entities")
-app.include_router(query_router, prefix="/brickapi/v1/rawqueries")
-app.include_router(actuation_router, prefix="/brickapi/v1/actuation")
-app.include_router(grafana_router, prefix="/brickapi/v1/grafana")
-app.include_router(auth_router, prefix="/brickapi/v1/auth")
-# app.include_router(dummy_frontend_router, prefix='/dummy-frontend')
-app.secret_key = os.urandom(24)
-app.add_middleware(SessionMiddleware, secret_key=os.urandom(24))
+backend_app: fastapi.FastAPI = initialize_backend_application()
diff --git a/brick_server/minimal/auth/auth_server.py b/brick_server/minimal/auth/auth_server.py
deleted file mode 100644
index e8b820f..0000000
--- a/brick_server/minimal/auth/auth_server.py
+++ /dev/null
@@ -1,221 +0,0 @@
-import time
-
-import arrow
-from fastapi import Form, Path, Query
-
-# from fastapi.responses import PlainTextResponse
-from fastapi.security import HTTPAuthorizationCredentials
-from fastapi_rest_framework.config import settings
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
-from starlette.requests import Request
-from starlette.responses import RedirectResponse
-
-from brick_server.minimal.auth.authorization import (
- FRONTEND_APP,
- authorized_frontend,
- create_jwt_token,
- jwt_security_scheme,
- oauth,
- parse_jwt_token,
-)
-from brick_server.minimal.exceptions import DoesNotExistError, NotAuthorizedError
-from brick_server.minimal.models import AppToken, User, get_doc, get_docs
-from brick_server.minimal.schemas import IsSuccess, TokenResponse, TokensResponse
-
-auth_router = InferringRouter()
-# auth_base_url = settings.hostname + "/auth"
-frontend_url = settings.frontend
-
-
-# @auth_router.get('/jwt_pubkey',
-# status_code=200,
-# description='Get the current JWT Public Key',
-# tags=['Auth'],
-# )
-# def get_jwt_pubkey():
-# return PlainTextResponse(_jwt_pub_key, media_type='text/plain')
-
-
-@auth_router.get(
- "/login",
- tags=["Auth"],
-)
-async def get_login_via_google(request: Request):
- redirect_uri = request.url_for("get_is_registered")
- print(redirect_uri)
- return {"redirect_uri": redirect_uri}
- # res = await oauth.google.authorize_redirect(request, redirect_uri)
- # return res
-
-
-@auth_router.get(
- "/is_registered",
- status_code=302,
- response_class=RedirectResponse,
- tags=["Auth"],
-)
-async def get_is_registered(request: Request):
- token = await oauth.google.authorize_access_token(request)
- t0 = time.time()
- user = await oauth.google.parse_id_token(request, token)
- t1 = time.time()
- print("parsing token took: {} seconds".format(t1 - t0))
- params = {
- "access_token": token["access_token"],
- }
- # resp = requests.get(oauth.google.api_base_url + '/userinfo', params=params)
- assert user["email_verified"]
- try:
- user_doc = get_doc(User, user_id=user["email"])
- redirect_uri = frontend_url + "/logged-in-success"
- app_token_str = create_jwt_token(
- user_id=user["email"],
- app_name=FRONTEND_APP,
- ).decode("utf-8")
- redirect_uri += "?app_token=" + app_token_str
- return RedirectResponse(redirect_uri)
- except DoesNotExistError:
- request.session["access_token"] = token
- profile = (await oauth.google.get("userinfo", token=token)).json()
- redirect_uri = frontend_url + "/register"
- return RedirectResponse(redirect_uri)
-
-
-@auth_router.get("/logincallback") # NOTE: Dummy function
-async def get_authorize(request: Request):
- token = await oauth.google.authorize_access_token(request)
- user = await oauth.google.parse_id_token(request, token)
- request.session["id_token"] = token
- return dict(user)
-
-
-@cbv(auth_router)
-class AppTokenRouter(object):
- @auth_router.delete(
- "/app_tokens/{app_token}",
- status_code=200,
- tags=["Auth"],
- response_model=IsSuccess,
- )
- @authorized_frontend
- async def del_token(
- self,
- app_token: str = Path(..., description="Token to delete."),
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- ) -> IsSuccess:
- # user = await _get_id_token_user(request) TODO
- user_id = parse_jwt_token(token.credentials)["user_id"]
- user = get_doc(User, user_id=user_id)
- token_doc = get_doc(AppToken, user=user, token=app_token)
- token_doc.delete()
- # TODO: Register deleted token in a db to check in runtime.
- return IsSuccess()
-
-
-@cbv(auth_router)
-class AppTokensRouter(object):
- @auth_router.post(
- "/app_tokens",
- status_code=200,
- tags=["Auth"],
- )
- @authorized_frontend
- async def gen_token(
- self,
- app_name: str = Query(
- "", description="The name of an app the user needs to generate a token for"
- ),
- token_lifetime: int = Query(
- 3600,
- description="Expiration time of the requested token in seconds.",
- ),
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- ) -> TokenResponse:
- user_id = parse_jwt_token(token.credentials)["user_id"]
- app_token_str = create_jwt_token(
- token_lifetime=token_lifetime, app_name=app_name
- )
- user = get_doc(User, user_id=user_id)
- app_token = AppToken(
- user=user,
- token=app_token_str,
- name=app_name,
- )
- app_token.save()
- payload = parse_jwt_token(app_token_str)
- return TokenResponse(token=app_token_str, exp=payload["exp"], name=app_name)
-
- @auth_router.get(
- "/app_tokens",
- status_code=200,
- tags=["Auth"],
- response_model=TokensResponse,
- )
- @authorized_frontend
- async def get_tokens(
- self,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- ) -> TokensResponse:
- # user = await _get_id_token_user(request) TODO
- user_id = parse_jwt_token(token.credentials)["user_id"]
- user = get_doc(User, user_id=user_id)
-
- app_tokens = []
- for app_token in get_docs(AppToken, user=user):
- try:
- payload = parse_jwt_token(app_token.token)
- app_tokens.append(
- TokenResponse(
- token=app_token.token,
- name=app_token.name,
- exp=payload["exp"],
- )
- )
- except NotAuthorizedError as e:
- if e.detail == "The token has been expired":
- app_token.delete()
- else:
- raise e
- return app_tokens
-
-
-@auth_router.get(
- "/register",
- status_code=302,
- response_class=RedirectResponse,
- tags=["Auth"],
-)
-async def post_register_user(
- request: Request,
- is_admin: bool = Form(
- False, description="Designate if the user is going to be an admin or not."
- ),
-):
- # TODO: Check if is_admin is allowed somwehow. (Maybe endorsed by the first admin or check the number of admins in the database and allow only one.
- token = request.session["access_token"]
- oauth_user = await oauth.google.parse_id_token(request, token)
- profile = (await oauth.google.get("userinfo", token=token)).json()
-
- if is_admin:
- assert (
- User.objects.count(is_admin=True) == 0
- ), "There is already an existnig admin, and Brick Server currently allows only on eadmin"
- is_approved = True
- else:
- is_approved = False
- new_user = User(
- name=profile["name"],
- user_id=oauth_user["email"],
- email=oauth_user["email"],
- is_admin=is_admin,
- is_approved=False,
- registration_time=arrow.get().datetime,
- )
- new_user.save()
- app_token_str = create_jwt_token(
- user_id=profile["email"],
- app_name=FRONTEND_APP,
- ).decode("utf-8")
- redirect_uri = frontend_url + "/logged-in-success?app_token=" + app_token_str
- return RedirectResponse(redirect_uri)
diff --git a/brick_server/minimal/auth/authorization.py b/brick_server/minimal/auth/authorization.py
deleted file mode 100644
index 83cc3f1..0000000
--- a/brick_server/minimal/auth/authorization.py
+++ /dev/null
@@ -1,318 +0,0 @@
-import abc
-import asyncio
-import time
-from enum import Enum
-from functools import wraps
-from typing import Callable, Set
-
-import arrow
-import jwt
-from fastapi import Body, Depends, HTTPException, Query, Security
-from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
-from fastapi_rest_framework.config import settings
-
-from brick_server.minimal.descriptions import Descriptions
-from brick_server.minimal.models import User, get_doc
-
-from ..exceptions import (
- NotAuthorizedError,
- TokenSignatureExpired,
- TokenSignatureInvalid,
-)
-
-FRONTEND_APP = "brickserver_frontend"
-
-A = "A" # actuatable
-W = "W" # writable
-R = "R" # readable
-O = "O" # owning
-
-auth_scheme = HTTPBearer(bearerFormat="JWT")
-
-
-class PermissionType(str, Enum):
- read = "read"
- write = "write"
- unknown = "unknown"
-
-
-# if False:
-# # if configs["auth"].get("oauth_connections", None):
-# google_config = configs["auth"]["oauth_connections"]["google"]
-# oauth = OAuth()
-# oauth.register(
-# name="google",
-# client_id=google_config["client_id"],
-# client_secret=google_config["client_secret"],
-# api_base_url=google_config["api_base_url"],
-# request_token_url=None,
-# request_token_params={
-# "scope": "email openid profile",
-# "access_type": "offline",
-# "prompt": "consent",
-# },
-# access_token_url=google_config["access_token_url"],
-# authorize_url=google_config["authorize_url"],
-# client_kwargs=google_config["client_kwargs"],
-# jwks_uri=google_config["jwks_uri"],
-# access_type="offline",
-# prompt="consent",
-# )
-# else:
-oauth = None
-
-# privkey_path = configs["auth"]["jwt"].get("privkey_path", "configs/jwtRS256.key")
-# pubkey_path = configs["auth"]["jwt"].get("pubkey_path", "configs/jwtRS256.key.pub")
-# with open(privkey_path, "r") as fp:
-# _jwt_priv_key = fp.read()
-# with open(pubkey_path, "r") as fp:
-# _jwt_pub_key = fp.read()
-
-
-# def _get_jwt_token_user(token):
-# payload = parse_jwt_token(kwargs["token"].credentials)
-# return payload["user_id"]
-
-
-def authorized_dep(permission_required, get_entity_ids=None):
- def auth_enabled_decorator(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- # Intentionally empty not to check anything as a dummy authorization
- return f(*args, **kwargs)
-
- return decorated_function
-
- return auth_enabled_decorator
-
-
-def create_jwt_token(
- user_id: str = "admin",
- app_name: str = None,
- token_lifetime: int = settings.jwt_expire_seconds,
-):
- payload = {
- "user_id": user_id,
- "exp": time.time() + token_lifetime, # TODO: Think about the timezone
- "app_id": app_name,
- }
- jwt_token = jwt.encode(
- payload, settings.jwt_secret, algorithm=settings.jwt_algorithm
- )
- return jwt_token
-
-
-def parse_jwt_token(jwt_token):
- try:
- payload = jwt.decode(
- jwt_token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]
- )
- except jwt.exceptions.InvalidSignatureError as e:
- raise NotAuthorizedError(detail="The token's signature is invalid.")
- except jwt.exceptions.ExpiredSignatureError as e:
- raise NotAuthorizedError(detail="The token has been expired")
- except Exception as e:
- raise HTTPException(status_code=400, detail="The token is invalid")
- return payload
-
-
-# A list of auth_logics
-
-
-# def auth_logic_template(action_type, target_ids, *args, **kwargs):
-# raise Exception(
-# "Not Implemented and this is not meant to be used but just for reference."
-# )
-
-jwt_security_scheme = Security(auth_scheme)
-
-
-def validate_token(token: "jwt_security_scheme"):
- try:
- payload = parse_jwt_token(token.credentials)
- except jwt.exceptions.InvalidSignatureError as e:
- raise NotAuthorizedError(detail="Given JWT token is not valid")
- return True
-
-
-def default_auth_logic(
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
-) -> Callable[[Set[str], PermissionType], bool]:
- def _auth_logic(entity_ids: Set[str], permission: PermissionType):
- return validate_token(token)
-
- return _auth_logic
-
-
-def authorized_frontend(f):
- @wraps(f)
- async def decorated(*args, **kwargs):
- # Intentionally empty not to check anything as a dummy authorization
- try:
- payload = parse_jwt_token(kwargs["token"].credentials)
- except jwt.exceptions.InvalidSignatureError:
- raise TokenSignatureInvalid()
- except jwt.exceptions.ExpiredSignatureError:
- raise TokenSignatureExpired()
- user_id = payload["user_id"]
- app_name = payload["app_id"]
- if app_name != FRONTEND_APP:
- raise HTTPException(
- status_code=401,
- detail=f'This token is not for the app "{FRONTEND_APP}".',
- )
- return await f(*args, **kwargs)
-
- return decorated
-
-
-def authorized_admin(f):
- @wraps(f)
- async def decorated(*args, **kwargs):
- # Intentionally empty not to check anything as a dummy authorization
- payload = parse_jwt_token(kwargs["token"].credentials)
- user_id = payload["user_id"]
- user = get_doc(User, user_id=user_id)
- if not user.is_admin:
- raise HTTPException(
- status_code=401,
- detail="{user_id} does not have the right permission.",
- )
- return await f(*args, **kwargs)
-
- return decorated
-
-
-def default_get_target_ids(*args, **kwargs):
- return [kwargs["entity_id"]]
-
-
-def authorized_arg(permission_type, get_target_ids=default_get_target_ids):
- def auth_wrapper(f):
- @wraps(f)
- async def decorated(*args, **kwargs):
- # Intentionally empty not to check anything as a dummy authorization
- self = kwargs["self"]
- # jwt_token = parse_jwt_token(kwargs['token'].credentials)
- target_ids = get_target_ids(*args, **kwargs)
- if not self.auth_logic(permission_type, target_ids, *args, **kwargs):
- raise HTTPException(
- status_code=401,
- detail="{user_id} does not have the right permission.",
- )
- return await f(*args, **kwargs)
-
- return decorated
-
- return auth_wrapper
-
-
-class PermissionCheckerBase(abc.ABC):
- def __init__(self, permission_type: PermissionType = PermissionType.unknown):
- self.permission_type = permission_type
-
- @staticmethod
- async def call_auth_logic(
- auth_logic, entity_ids: Set[str], permission: PermissionType
- ):
- if asyncio.iscoroutinefunction(auth_logic):
- return await auth_logic(entity_ids, permission)
- return auth_logic(entity_ids, permission)
-
-
-class PermissionChecker(PermissionCheckerBase):
- from brick_server.minimal.dependencies import dependency_supplier
-
- async def __call__(
- self,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- auth_logic: Callable[[Set[str], PermissionType], bool] = Depends(
- dependency_supplier.auth_logic
- ),
- ):
- await self.call_auth_logic(auth_logic, set(), self.permission_type)
-
-
-class PermissionCheckerWithEntityId(PermissionCheckerBase):
- from brick_server.minimal.dependencies import dependency_supplier
-
- async def __call__(
- self,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- auth_logic: Callable[[Set[str], PermissionType], bool] = Depends(
- dependency_supplier.auth_logic
- ),
- entity_id: str = Query(..., description=""),
- ):
- await self.call_auth_logic(auth_logic, {entity_id}, self.permission_type)
-
-
-class PermissionCheckerWithData(PermissionCheckerBase):
- from brick_server.minimal.dependencies import dependency_supplier
- from brick_server.minimal.schemas import TimeseriesData
-
- @staticmethod
- def get_entity_ids(data: TimeseriesData) -> Set[str]:
- rows = data.data
- columns = data.columns
- uuid_idx = columns.index("uuid")
- uuids = {row[uuid_idx] for row in rows}
- return uuids
-
- async def __call__(
- self,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- auth_logic: Callable[[Set[str], PermissionType], bool] = Depends(
- dependency_supplier.auth_logic
- ),
- data: TimeseriesData = Body(..., description=Descriptions.timeseries_data),
- ):
- entity_ids = self.get_entity_ids(data)
- await self.call_auth_logic(auth_logic, entity_ids, self.permission_type)
-
-
-def authorized(f):
- @wraps(f)
- async def decorated(*args, **kwargs):
- # Intentionally empty not to check anything as a dummy authorization
- self = kwargs["self"]
- # jwt_token = parse_jwt_token(kwargs['token'].credentials)
- if not self.auth_logic(None, [], *args, **kwargs):
- raise HTTPException(
- status_code=401,
- detail="{user_id} does not have the right permission.",
- )
- return await f(*args, **kwargs)
-
- return decorated
-
-
-def authenticated(f):
- @wraps(f)
- async def decorated(*args, **kwargs):
- # Intentionally empty not to check anything as a dummy authorization
- payload = parse_jwt_token(kwargs["token"].credentials)
- user = get_doc(User, user_id=payload["user_id"])
- # TODO: Activate below.
- # if not user.is_approved:
- # raise UserNotApprovedError(status_code=401, detail='The user account has not been approved by the admin yet.')
- return await f(*args, **kwargs)
-
- return decorated
-
-
-def create_user(name, user_id, email, is_admin=False):
- created_user = User(
- name=name,
- user_id=user_id,
- email=email,
- is_admin=is_admin,
- registration_time=arrow.get().datetime,
- )
- created_user.save()
-
-
-async def _get_id_token_user(request):
- id_token = request.session["id_token"]
- oauth_user = await oauth.google.parse_id_token(request, id_token)
- return get_doc(User, user_id=oauth_user["email"])
diff --git a/brick_server/minimal/config.py b/brick_server/minimal/config.py
deleted file mode 100644
index 64bec52..0000000
--- a/brick_server/minimal/config.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from typing import Type, Union
-
-from fastapi_rest_framework import config
-
-
-@config.add
-class BaseConfig(config.Base):
- """
- Server configuration
-
- The configuration of server connection, debug mode and proxy
- """
-
- debug: bool = False
- host: str = "localhost"
- port: int = 9000
- workers: int = 4
-
- default_brick_url: str = "https://brickschema.org/schema/Brick"
-
- hostname: str = "http://localhost:9000"
- frontend: str = "DUMMY-NOT-WORK"
-
-
-@config.add
-class AuthConfig(config.Base):
- """
- Auth configuration
-
- The configuration of JWT, OAuth
- """
-
- # jwt config
- jwt_secret: str = "secret"
- jwt_algorithm: str = "HS256"
- jwt_expire_seconds: int = 14 * 24 * 60 * 60 # 14 days, in seconds
-
- # oauth config
-
-
-@config.add
-class DatabaseConfig(config.Base):
- """
- Database configuration
- """
-
- mongo_host: str = "localhost"
- mongo_port: int = 27017
- mongo_username: str = ""
- mongo_password: str = ""
- mongo_dbname: str = "brickserver"
-
- timescale_host: str = "localhost"
- timescale_port: int = 5432
- timescale_username: str = "bricker"
- timescale_password: str = "brick-demo"
- timescale_dbname: str = "brick"
-
- brick_host: str = "localhost"
- brick_port: int = 8890
- brick_api_endpoint: str = "sparql"
- brick_version: str = "1.1"
- brick_base_ns: str = "bldg:"
- brick_base_graph: str = "brick-base-graph"
-
- graphdb_host: str = "localhost"
- graphdb_port: int = 7200
- graphdb_repository: str = "brickserver"
-
- grafana_host: str = "localhost"
- grafana_port: int = 3000
- grafana_api_endpoint: str = "api"
- grafana_api_key: str = "YOUR_API_TOKEN"
-
-
-FastAPIConfig: Type[
- Union[BaseConfig, AuthConfig, DatabaseConfig]
-] = config.generate_config_class(mixins=[config.EnvFileMixin, config.CLIMixin])
diff --git a/brick_server/minimal/auth/__init__.py b/brick_server/minimal/config/__init__.py
similarity index 100%
rename from brick_server/minimal/auth/__init__.py
rename to brick_server/minimal/config/__init__.py
diff --git a/brick_server/minimal/config/errors.py b/brick_server/minimal/config/errors.py
new file mode 100644
index 0000000..eb0d269
--- /dev/null
+++ b/brick_server/minimal/config/errors.py
@@ -0,0 +1,133 @@
+from typing import Any
+
+from fastapi import FastAPI, Request, status
+from fastapi.encoders import jsonable_encoder
+from fastapi.exceptions import HTTPException
+from fastapi.responses import JSONResponse
+from fastapi_users import exceptions as fastapi_users_exceptions
+from loguru import logger
+from pydantic import ValidationError
+
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode, ErrorShowType
+
+
+def business_error_response(exc: BizError) -> JSONResponse:
+ return JSONResponse(
+ jsonable_encoder(
+ {
+ "errorCode": exc.error_code,
+ "errorMessage": exc.error_message,
+ "showType": exc.show_type,
+ "data": {},
+ }
+ ),
+ status_code=status.HTTP_200_OK,
+ )
+
+
+def fastapi_users_error_handler(
+ request: Request, exc: fastapi_users_exceptions.FastAPIUsersException
+) -> JSONResponse:
+ try:
+ raise exc
+ except (fastapi_users_exceptions.UserNotExists, fastapi_users_exceptions.InvalidID):
+ return business_error_response(
+ BizError(ErrorCode.UserNotFoundError, show_type=ErrorShowType.ErrorMessage)
+ )
+ except fastapi_users_exceptions.UserAlreadyExists:
+ return business_error_response(
+ BizError(
+ ErrorCode.UserAlreadyExistsError, show_type=ErrorShowType.ErrorMessage
+ )
+ )
+ except fastapi_users_exceptions.InvalidPasswordException as e:
+ return business_error_response(
+ BizError(
+ ErrorCode.UserInvalidPasswordError, e.reason, ErrorShowType.ErrorMessage
+ )
+ )
+ except (
+ fastapi_users_exceptions.InvalidVerifyToken,
+ fastapi_users_exceptions.InvalidResetPasswordToken,
+ ):
+ return business_error_response(
+ BizError(
+ ErrorCode.InvalidTokenError,
+ exc.__class__.__name__,
+ ErrorShowType.ErrorMessage,
+ )
+ )
+ except fastapi_users_exceptions.UserAlreadyVerified:
+ return business_error_response(
+ BizError(
+ ErrorCode.UserAlreadyVerifiedError, show_type=ErrorShowType.WarnMessage
+ )
+ )
+ except fastapi_users_exceptions.UserInactive:
+ return business_error_response(
+ BizError(ErrorCode.UserInactiveError, show_type=ErrorShowType.WarnMessage)
+ )
+ except fastapi_users_exceptions.FastAPIUsersException:
+ return business_error_response(
+ BizError(
+ ErrorCode.InternalServerError,
+ exc.__class__.__name__,
+ ErrorShowType.ErrorMessage,
+ )
+ )
+
+
+def business_error_handler(request: Request, exc: BizError) -> JSONResponse:
+ return business_error_response(exc)
+
+
+def validation_error_handler(request: Request, exc: ValidationError) -> JSONResponse:
+ logger.exception(exc)
+ return business_error_response(
+ BizError(
+ ErrorCode.ValidationError, str(exc.errors()), ErrorShowType.ErrorMessage
+ )
+ )
+
+
+def http_error_handler(request: Request, exc: HTTPException) -> JSONResponse:
+ if exc.status_code == status.HTTP_401_UNAUTHORIZED:
+ return business_error_response(
+ BizError(
+ ErrorCode.UnauthorizedError, exc.detail, ErrorShowType.ErrorMessage
+ )
+ )
+ elif exc.status_code == status.HTTP_403_FORBIDDEN:
+ return business_error_response(
+ BizError(ErrorCode.PermissionError, exc.detail, ErrorShowType.ErrorMessage)
+ )
+ else:
+ return business_error_response(
+ BizError(
+ ErrorCode.InternalServerError, exc.detail, ErrorShowType.ErrorMessage
+ )
+ )
+
+
+async def catch_exceptions_middleware(request: Request, call_next: Any) -> JSONResponse:
+ try:
+ return await call_next(request)
+ except Exception as e:
+ logger.exception(f"Unexpected Error: {e.__class__.__name__}")
+ return business_error_response(
+ BizError(
+ ErrorCode.InternalServerError,
+ e.__class__.__name__,
+ ErrorShowType.ErrorMessage,
+ )
+ )
+
+
+def register_error_handlers(backend_app: FastAPI) -> None:
+ backend_app.add_exception_handler(
+ fastapi_users_exceptions.FastAPIUsersException, fastapi_users_error_handler
+ )
+ backend_app.add_exception_handler(BizError, business_error_handler)
+ backend_app.add_exception_handler(ValidationError, validation_error_handler)
+ backend_app.add_exception_handler(HTTPException, http_error_handler)
+ backend_app.middleware("http")(catch_exceptions_middleware)
diff --git a/brick_server/minimal/config/events.py b/brick_server/minimal/config/events.py
new file mode 100644
index 0000000..f18084f
--- /dev/null
+++ b/brick_server/minimal/config/events.py
@@ -0,0 +1,33 @@
+import typing
+
+import fastapi
+import loguru
+
+from brick_server.minimal import models
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.interfaces.mongodb import initialize_mongodb
+from brick_server.minimal.interfaces.timeseries import (
+ dispose_timeseries,
+ initialize_timeseries,
+)
+
+
+def execute_backend_server_event_handler(backend_app: fastapi.FastAPI) -> typing.Any:
+ async def launch_backend_server_events() -> None:
+ loguru.logger.info("------ {} Initializing ------", settings.TITLE)
+ document_models = [models.User, models.Domain]
+ await initialize_mongodb(
+ backend_app=backend_app, document_models=document_models
+ )
+ await initialize_timeseries()
+ loguru.logger.info("------ {} Launched ------", settings.TITLE)
+
+ return launch_backend_server_events
+
+
+def terminate_backend_server_event_handler(backend_app: fastapi.FastAPI) -> typing.Any:
+ @loguru.logger.catch
+ async def stop_backend_server_events() -> None:
+ await dispose_timeseries()
+
+ return stop_backend_server_events
diff --git a/brick_server/minimal/config/manager.py b/brick_server/minimal/config/manager.py
new file mode 100644
index 0000000..95ead67
--- /dev/null
+++ b/brick_server/minimal/config/manager.py
@@ -0,0 +1,29 @@
+from functools import lru_cache
+
+import decouple
+
+from brick_server.minimal.config.settings.base import BackendBaseSettings
+from brick_server.minimal.config.settings.development import BackendDevSettings
+from brick_server.minimal.config.settings.environment import Environment
+from brick_server.minimal.config.settings.production import BackendProdSettings
+from brick_server.minimal.config.settings.staging import BackendStageSettings
+
+
+class BackendSettingsFactory:
+ def __init__(self, environment: str):
+ self.environment = environment
+
+ def __call__(self) -> BackendBaseSettings:
+ if self.environment == Environment.DEVELOPMENT.value:
+ return BackendDevSettings()
+ elif self.environment == Environment.STAGING.value:
+ return BackendStageSettings()
+ return BackendProdSettings()
+
+
+@lru_cache()
+def get_settings() -> BackendBaseSettings:
+ return BackendSettingsFactory(environment=decouple.config("ENVIRONMENT", default="DEV", cast=str))() # type: ignore
+
+
+settings: BackendBaseSettings = get_settings()
diff --git a/brick_server/minimal/config/settings/__init__.py b/brick_server/minimal/config/settings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/brick_server/minimal/config/settings/base.py b/brick_server/minimal/config/settings/base.py
new file mode 100644
index 0000000..202ed30
--- /dev/null
+++ b/brick_server/minimal/config/settings/base.py
@@ -0,0 +1,187 @@
+import logging
+import pathlib
+
+import decouple
+import pydantic
+import pydantic_settings
+
+ROOT_DIR: pathlib.Path = pathlib.Path(
+ __file__
+).parent.parent.parent.parent.parent.resolve()
+
+
+class BackendBaseSettings(pydantic_settings.BaseSettings):
+ TITLE: str = "Brick Server Minimal"
+ VERSION: str = "0.1.0"
+ TIMEZONE: str = "UTC"
+ DESCRIPTION: str = ""
+ DEBUG: bool = False
+ CACHE: bool = decouple.config("CACHE", cast=bool, default=True)
+
+ # brick
+ BRICK_VERSION: str = decouple.config("BRICK_VERSION", cast=str, default="1.3")
+ DEFAULT_BRICK_URL: str = decouple.config(
+ "DEFAULT_BRICK_URL", cast=str, default="https://brickschema.org/schema/Brick"
+ )
+ DEFAULT_REF_SCHEMA_URL: str = decouple.config(
+ "DEFAULT_REF_SCHEMA_URL",
+ cast=str,
+ default="https://gist.githubusercontent.com/tc-imba/714c2043e893b1538406a9113140a4fe/"
+ "raw/2fa8df840f3e4f1deb647b14fe524976f004e321/ref-schema.ttl",
+ )
+
+ # backend
+ SERVER_HOST: str = decouple.config("SERVER_HOST", cast=str, default="0.0.0.0")
+ SERVER_PORT: int = decouple.config("SERVER_PORT", cast=int, default=9000)
+ SERVER_WORKERS: int = decouple.config("SERVER_WORKERS", cast=int, default=1)
+ API_PREFIX: str = "/brickapi/v1"
+ DOCS_URL: str = "/brickapi/v1/docs"
+ OPENAPI_URL: str = "/brickapi/v1/openapi.json"
+ REDOC_URL: str = "/brickapi/v1/redoc"
+ OPENAPI_PREFIX: str = ""
+ FRONTEND_URL: str = decouple.config("FRONTEND_URL", cast=str, default=DOCS_URL)
+
+ # mongo
+ MONGO_HOST: str = decouple.config("MONGO_HOST", cast=str, default="localhost")
+ MONGO_PORT: int = decouple.config("MONGO_PORT", cast=int, default=27017)
+ MONGO_SCHEMA: str = decouple.config("MONGO_SCHEMA", cast=str, default="mongodb")
+ MONGO_DATABASE: str = decouple.config(
+ "MONGO_DATABASE", cast=str, default="brickserver"
+ )
+ MONGO_USERNAME: str = decouple.config("MONGO_USERNAME", cast=str, default="")
+ MONGO_PASSWORD: str = decouple.config("MONGO_PASSWORD", cast=str, default="")
+
+ # timescaledb
+ TIMESCALE_HOST: str = decouple.config(
+ "TIMESCALE_HOST", cast=str, default="localhost"
+ )
+ TIMESCALE_PORT: int = decouple.config("TIMESCALE_PORT", cast=int, default=5432)
+ TIMESCALE_USERNAME: str = decouple.config(
+ "TIMESCALE_USERNAME", cast=str, default="bricker"
+ )
+ TIMESCALE_PASSWORD: str = decouple.config(
+ "TIMESCALE_PASSWORD", cast=str, default="brick-demo"
+ )
+ TIMESCALE_DATABASE: str = decouple.config(
+ "TIMESCALE_DATABASE", cast=str, default="brick"
+ )
+
+ # influxdb
+ INFLUXDB_URL: str = decouple.config(
+ "INFLUXDB_URL",
+ cast=str,
+ default="https://us-east-1-1.aws.cloud2.influxdata.com",
+ )
+ INFLUXDB_TOKEN: str = decouple.config("INFLUXDB_TOKEN", cast=str, default="")
+ INFLUXDB_ORG: str = decouple.config(
+ "INFLUXDB_ORG", cast=str, default="9d4d3af8fd50fcbb"
+ )
+ INFLUXDB_BUCKET: str = decouple.config(
+ "INFLUXDB_BUCKET", cast=str, default="CO2-Exp"
+ )
+
+ # redis
+ REDIS_HOST: str = decouple.config("TIMESCALE_HOST", cast=str, default="localhost")
+ REDIS_PORT: int = decouple.config("REDIS_PORT", cast=int, default=6379)
+ REDIS_PASSWORD: str = decouple.config(
+ "REDIS_PASSWORD", cast=str, default="brick-demo"
+ )
+ REDIS_DATABASE: int = decouple.config("REDIS_DATABASE", cast=int, default=0)
+
+ # graphdb
+ GRAPHDB_HOST: str = decouple.config("GRAPHDB_HOST", cast=str, default="localhost")
+ GRAPHDB_PORT: int = decouple.config("GRAPHDB_PORT", cast=int, default=7200)
+ GRAPHDB_REPOSITORY: str = decouple.config(
+ "GRAPHDB_REPOSITORY", cast=str, default="brickserver"
+ )
+
+ # # db
+ # DB_MAX_POOL_CON: int = decouple.config("DB_MAX_POOL_CON", cast=int, default=5) # type: ignore
+ # DB_POOL_SIZE: int = decouple.config("DB_POOL_SIZE", cast=int, default=100) # type: ignore
+ # DB_POOL_OVERFLOW: int = decouple.config("DB_POOL_OVERFLOW", cast=int, default=80) # type: ignore
+ # DB_TIMEOUT: int = decouple.config("DB_TIMEOUT", cast=int, default=20) # type: ignore
+ # IS_DB_ECHO_LOG: bool = decouple.config("IS_DB_ECHO_LOG", cast=bool, default=False) # type: ignore
+ # IS_DB_FORCE_ROLLBACK: bool = decouple.config("IS_DB_FORCE_ROLLBACK", cast=bool, default=True) # type: ignore
+ # IS_DB_EXPIRE_ON_COMMIT: bool = decouple.config("IS_DB_EXPIRE_ON_COMMIT", cast=bool, default=True) # type: ignore
+ #
+ # # s3
+ # S3_HOST: str = decouple.config("S3_HOST", cast=str, default="127.0.0.1") # type: ignore
+ # S3_PORT: int = decouple.config("S3_PORT", cast=int, default=9000) # type: ignore
+ # S3_USERNAME: str = decouple.config("S3_USERNAME", cast=str, default="minioadmin") # type: ignore
+ # S3_PASSWORD: str = decouple.config("S3_PASSWORD", cast=str, default="minioadmin") # type: ignore
+ # S3_BUCKET: str = decouple.config("S3_BUCKET", cast=str, default="brick") # type: ignore
+ # S3_PUBLIC_URL: str = decouple.config("S3_PUBLIC_URL", cast=str, default="http://localhost:9000") # type: ignore
+
+ # auth
+ API_TOKEN: str = decouple.config("API_TOKEN", cast=str, default="YOUR-API-TOKEN") # type: ignore
+ AUTH_TOKEN: str = decouple.config("AUTH_TOKEN", cast=str, default="YOUR-AUTHENTICATION-TOKEN") # type: ignore
+ JWT_SECRET_KEY: str = decouple.config("JWT_SECRET_KEY", cast=str, default="YOUR-JWT-SECRET-KEY") # type: ignore
+ JWT_SUBJECT: str = decouple.config("JWT_SUBJECT", cast=str, default="brick") # type: ignore
+ JWT_TOKEN_PREFIX: str = decouple.config("JWT_TOKEN_PREFIX", cast=str, default="brick") # type: ignore
+ JWT_ALGORITHM: str = decouple.config("JWT_ALGORITHM", cast=str, default="HS256") # type: ignore
+ JWT_MIN: int = decouple.config("JWT_MIN", cast=int, default=0) # type: ignore
+ JWT_HOUR: int = decouple.config("JWT_HOUR", cast=int, default=0) # type: ignore
+ JWT_DAY: int = decouple.config("JWT_DAY", cast=int, default=14) # type: ignore
+ JWT_EXPIRE_SECONDS: int = ((JWT_DAY * 24 + JWT_HOUR) * 60 + JWT_MIN) * 60
+
+ # oauth
+ OAUTH_GOOGLE_CLIENT_ID: str = decouple.config(
+ "OAUTH_GOOGLE_CLIENT_ID", cast=str, default=""
+ )
+ OAUTH_GOOGLE_CLIENT_SECRET: str = decouple.config(
+ "OAUTH_GOOGLE_CLIENT_SECRET", cast=str, default=""
+ )
+
+ JAAS_APP_ID: str = decouple.config("JAAS_APP_ID", cast=str, default="YOUR-JAAS-APP-ID") # type: ignore
+ JAAS_API_KEY: str = decouple.config("JAAS_API_KEY", cast=str, default="YOUR-JAAS-API-KEY") # type: ignore
+ JAAS_PRIVATE_KEY_PATH: str = decouple.config(
+ "JAAS_PRIVATE_KEY_PATH", cast=str, default="YOUR-JAAS-PRIVATE-KEY-PATH"
+ ) # type: ignore
+ IS_ALLOWED_CREDENTIALS: bool = decouple.config("IS_ALLOWED_CREDENTIALS", cast=bool, default=True) # type: ignore
+ HASHING_ALGORITHM_LAYER_1: str = decouple.config(
+ "HASHING_ALGORITHM_LAYER_1", cast=str, default="bcrypt"
+ ) # type: ignore
+ HASHING_ALGORITHM_LAYER_2: str = decouple.config(
+ "HASHING_ALGORITHM_LAYER_2", cast=str, default="argon2"
+ ) # type: ignore
+ HASHING_SALT: str = decouple.config("HASHING_SALT", cast=str, default="YOUR-RANDOM-SALTY-SALT") # type: ignore
+
+ ALLOWED_ORIGINS: list[str] = [
+ "http://localhost:3000", # React default port
+ "http://0.0.0.0:3000",
+ "http://127.0.0.1:3000", # React docker port
+ "http://127.0.0.1:3001",
+ "http://localhost:5173", # Qwik default port
+ "http://0.0.0.0:5173",
+ "http://127.0.0.1:5173", # Qwik docker port
+ "http://127.0.0.1:5174",
+ ]
+ ALLOWED_METHODS: list[str] = ["*"]
+ ALLOWED_HEADERS: list[str] = ["*"]
+
+ LOGGING_LEVEL: int = logging.INFO
+ LOGGERS: tuple[str, str] = ("uvicorn.asgi", "uvicorn.access")
+
+ class Config(pydantic.BaseConfig):
+ case_sensitive: bool = True
+ env_file: str = f"{str(ROOT_DIR)}/.env"
+ validate_assignment: bool = True
+ extra: str = "allow"
+
+ @property
+ def set_backend_app_attributes(self) -> dict[str, str | bool | dict | None]:
+ """
+ Set all `FastAPI` class' attributes with the custom values defined in `BackendBaseSettings`.
+ """
+ return {
+ "title": self.TITLE,
+ "version": self.VERSION,
+ "debug": self.DEBUG,
+ "description": self.DESCRIPTION,
+ "docs_url": self.DOCS_URL,
+ "openapi_url": self.OPENAPI_URL,
+ "redoc_url": self.REDOC_URL,
+ "openapi_prefix": self.OPENAPI_PREFIX,
+ "api_prefix": self.API_PREFIX,
+ "swagger_ui_parameters": {"docExpansion": "none"},
+ }
diff --git a/brick_server/minimal/config/settings/development.py b/brick_server/minimal/config/settings/development.py
new file mode 100644
index 0000000..0262e5d
--- /dev/null
+++ b/brick_server/minimal/config/settings/development.py
@@ -0,0 +1,8 @@
+from brick_server.minimal.config.settings.base import BackendBaseSettings
+from brick_server.minimal.config.settings.environment import Environment
+
+
+class BackendDevSettings(BackendBaseSettings):
+ DESCRIPTION: str | None = "Development Environment."
+ DEBUG: bool = True
+ ENVIRONMENT: Environment = Environment.DEVELOPMENT
diff --git a/brick_server/minimal/config/settings/environment.py b/brick_server/minimal/config/settings/environment.py
new file mode 100644
index 0000000..c0e2c58
--- /dev/null
+++ b/brick_server/minimal/config/settings/environment.py
@@ -0,0 +1,7 @@
+import enum
+
+
+class Environment(str, enum.Enum):
+ PRODUCTION: str = "PROD" # type: ignore
+ DEVELOPMENT: str = "DEV" # type: ignore
+ STAGING: str = "STAGE" # type:ignore
diff --git a/brick_server/minimal/config/settings/production.py b/brick_server/minimal/config/settings/production.py
new file mode 100644
index 0000000..782d8d0
--- /dev/null
+++ b/brick_server/minimal/config/settings/production.py
@@ -0,0 +1,11 @@
+import decouple
+
+from brick_server.minimal.config.settings.base import BackendBaseSettings
+from brick_server.minimal.config.settings.environment import Environment
+
+
+class BackendProdSettings(BackendBaseSettings):
+ DESCRIPTION: str | None = "Production Environment."
+ ENVIRONMENT: Environment = Environment.PRODUCTION
+
+ SERVER_WORKERS: int = decouple.config("SERVER_WORKERS", cast=int, default=4)
diff --git a/brick_server/minimal/config/settings/staging.py b/brick_server/minimal/config/settings/staging.py
new file mode 100644
index 0000000..e0378ba
--- /dev/null
+++ b/brick_server/minimal/config/settings/staging.py
@@ -0,0 +1,8 @@
+from brick_server.minimal.config.settings.base import BackendBaseSettings
+from brick_server.minimal.config.settings.environment import Environment
+
+
+class BackendStageSettings(BackendBaseSettings):
+ DESCRIPTION: str | None = "Test Environment."
+ DEBUG: bool = True
+ ENVIRONMENT: Environment = Environment.STAGING
diff --git a/brick_server/minimal/dbs.py b/brick_server/minimal/dbs.py
deleted file mode 100644
index 7b6b30f..0000000
--- a/brick_server/minimal/dbs.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from fastapi_rest_framework.config import settings
-from mongoengine import connect as mongo_connect
-
-from brick_server.minimal.interfaces import AsyncpgTimeseries, RealActuation
-from brick_server.minimal.interfaces.grafana import GrafanaEndpoint
-from brick_server.minimal.interfaces.graphdb import GraphDB
-
-mongo_connection = mongo_connect(
- host=settings.mongo_host,
- port=settings.mongo_port,
- username=settings.mongo_username,
- password=settings.mongo_password,
- db=settings.mongo_dbname,
- connect=False,
-)
-
-
-actuation_iface = RealActuation()
-
-# brick_configs = configs["brick"]
-brick_url = (
- f"http://{settings.brick_host}:{settings.brick_port}/{settings.brick_api_endpoint}"
-)
-
-# brick_sparql = BrickSparqlAsync(
-# brick_url,
-# settings.brick_version,
-# graph=settings.brick_base_graph,
-# base_ns=settings.brick_base_ns,
-# )
-#
-# brick_sparql_sync = BrickSparql(
-# brick_url,
-# settings.brick_version,
-# graph=settings.brick_base_graph,
-# base_ns=settings.brick_base_ns,
-# )
-
-graphdb = GraphDB(
- host=settings.graphdb_host,
- port=settings.graphdb_port,
- repository=settings.graphdb_repository,
-)
-
-# brick_ts_configs = configs["timeseries"]
-ts_db = AsyncpgTimeseries(
- settings.timescale_dbname,
- settings.timescale_username,
- settings.timescale_password,
- settings.timescale_host,
- settings.timescale_port,
-)
-
-
-grafana_url = f"http://{settings.grafana_host}:{settings.grafana_port}/{settings.grafana_api_endpoint}"
-grafana_endpoint = GrafanaEndpoint(grafana_url, settings.grafana_api_key)
diff --git a/brick_server/minimal/dependencies.py b/brick_server/minimal/dependencies.py
deleted file mode 100644
index 15464d5..0000000
--- a/brick_server/minimal/dependencies.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from typing import Any, Callable, Dict, Set
-
-from fastapi import Path, Query
-from fastapi.security import HTTPAuthorizationCredentials
-
-from brick_server.minimal.auth.authorization import (
- PermissionType,
- default_auth_logic,
- jwt_security_scheme,
- parse_jwt_token,
-)
-from brick_server.minimal.interfaces import AsyncpgTimeseries, GraphDB
-from brick_server.minimal.models import Domain, get_doc
-
-auth_logic_func_type = Callable[[Set[str], PermissionType], bool]
-
-
-class DependencySupplier(object):
- auth_logic: Callable[[], auth_logic_func_type]
-
- # def get_auth_logic(self) -> Callable[[Set[str], PermissionType], bool]:
- # return self.auth_logic
-
-
-dependency_supplier = DependencySupplier()
-dependency_supplier.auth_logic = default_auth_logic
-
-
-def update_dependency_supplier(func: Callable[[], auth_logic_func_type]):
- dependency_supplier.auth_logic = func
-
-
-def get_ts_db() -> AsyncpgTimeseries:
- from brick_server.minimal.dbs import ts_db
-
- return ts_db
-
-
-def get_graphdb() -> GraphDB:
- from brick_server.minimal.dbs import graphdb
-
- return graphdb
-
-
-def get_actuation_iface():
- from brick_server.minimal.dbs import actuation_iface
-
- return actuation_iface
-
-
-def get_grafana():
- from brick_server.minimal.dbs import grafana_endpoint
-
- return grafana_endpoint
-
-
-def get_jwt_payload(
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
-) -> Dict[str, Any]:
- return parse_jwt_token(token.credentials)
-
-
-def query_domain(domain: str = Query(...)) -> Domain:
- return get_doc(Domain, name=domain)
-
-
-def path_domain(domain: str = Path(...)) -> Domain:
- return get_doc(Domain, name=domain)
diff --git a/brick_server/minimal/docker/init_postgis.sh b/brick_server/minimal/docker/init_postgis.sh
deleted file mode 100644
index 968dd05..0000000
--- a/brick_server/minimal/docker/init_postgis.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-if [ "${POSTGRES_DB:-postgres}" != 'postgres' ]; then
- psql -U "${POSTGRES_USER}" "${POSTGRES_DB}" -c "CREATE EXTENSION IF NOT EXISTS postgis CASCADE;"
-fi
diff --git a/brick_server/minimal/dummy_frontend.py b/brick_server/minimal/dummy_frontend.py
deleted file mode 100644
index a3993a5..0000000
--- a/brick_server/minimal/dummy_frontend.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from fastapi_rest_framework.config import settings
-from fastapi_utils.inferring_router import InferringRouter
-from starlette.requests import Request
-from starlette.responses import HTMLResponse
-
-frontend_hostname = settings.frontend
-loggedin_frontend = frontend_hostname + "/loggedin_page"
-
-
-dummy_frontend_router = InferringRouter(prefix="/dummy-frontend")
-
-
-@dummy_frontend_router.get(
- "/loggedin_page"
-) # TODO: This is supposed to be a main page for the user in the frontend.
-def get_dummy_register_user(
- request: Request,
- app_token: str,
-):
- return f"Frontend App token: {app_token}"
-
-
-login_link_tag = 'login'.format(settings.hostname)
-
-
-@dummy_frontend_router.get("/main")
-def login_main(
- request: Request,
- response_class: HTMLResponse,
-):
- return HTMLResponse(login_link_tag)
-
-
-@dummy_frontend_router.get("/register")
-def login_main_register(
- request: Request,
- name: str,
- email: str,
-):
- return HTMLResponse("Please register here")
diff --git a/brick_server/minimal/exceptions.py b/brick_server/minimal/exceptions.py
deleted file mode 100644
index f9a2ba6..0000000
--- a/brick_server/minimal/exceptions.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from fastapi import HTTPException
-
-
-class BrickServerError(Exception):
- def __init__(self, *args, **kwargs):
- super(BrickServerError, self).__init__(*args, **kwargs)
-
-
-class DoesNotExistError(BrickServerError, HTTPException):
- def __init__(self, klass, name, *args, **kwargs):
- if "detail" not in kwargs:
- kwargs["detail"] = f"{name} of {klass} does not exist."
- super(DoesNotExistError, self).__init__(status_code=404, *args, **kwargs)
-
-
-class AlreadyExistsError(BrickServerError, HTTPException):
- def __init__(self, klass, name, *args, **kwargs):
- if "detail" not in kwargs:
- kwargs["detail"] = f"{name} of {klass} already exists."
- super(AlreadyExistsError, self).__init__(status_code=409, *args, **kwargs)
-
-
-class MultipleObjectsFoundError(BrickServerError, HTTPException):
- def __init__(self, klass, name, *args, **kwargs):
- kwargs["detail"] = "There are multiple isntances of {} of {}".format(
- name, klass
- )
- super(MultipleObjectsFoundError, self).__init__(
- status_code=400, *args, **kwargs
- )
-
- def __str__(self):
- return repr(
- "There are multiple isntances of {} of {}".format(self.name, self.klass)
- )
-
-
-class UserNotApprovedError(BrickServerError, HTTPException):
- def __init__(self, *args, **kwargs):
- super(UserNotApprovedError, self).__init__(*args, **kwargs)
-
-
-class NotAuthorizedError(BrickServerError, HTTPException):
- def __init__(self, **kwargs):
- super(NotAuthorizedError, self).__init__(status_code=401, **kwargs)
-
-
-class TokenSignatureInvalid(NotAuthorizedError):
- def __init__(self, *args, **kwargs):
- if "detail" not in kwargs:
- kwargs["detail"] = "The token signature is invalid."
- super(TokenSignatureInvalid, self).__init__(*args, **kwargs)
-
-
-class TokenSignatureExpired(NotAuthorizedError):
- def __init__(self, *args, **kwargs):
- if "detail" not in kwargs:
- kwargs["detail"] = "The token signature has expired."
- super(TokenSignatureExpired, self).__init__(*args, **kwargs)
diff --git a/brick_server/minimal/helpers.py b/brick_server/minimal/helpers.py
deleted file mode 100644
index f17c085..0000000
--- a/brick_server/minimal/helpers.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def striding_windows(l, w_size):
- curr_idx = 0
- while curr_idx < len(l):
- yield l[curr_idx : curr_idx + w_size]
- curr_idx += w_size
diff --git a/brick_server/minimal/interfaces/__init__.py b/brick_server/minimal/interfaces/__init__.py
index 54e5224..db5944e 100644
--- a/brick_server/minimal/interfaces/__init__.py
+++ b/brick_server/minimal/interfaces/__init__.py
@@ -1,18 +1,15 @@
# nopycln: file
+from brick_server.minimal.interfaces.actuation.actuation_interface import (
+ ActuationInterface as ActuationInterface,
+)
from brick_server.minimal.interfaces.actuation.base_actuation import (
BaseActuation as BaseActuation,
)
-from brick_server.minimal.interfaces.actuation.dummy_actuation import (
- DummyActuation as DummyActuation,
-)
-from brick_server.minimal.interfaces.actuation.real_actuation import (
- RealActuation as RealActuation,
-)
from brick_server.minimal.interfaces.graphdb import GraphDB as GraphDB
-from brick_server.minimal.interfaces.timeseries.asyncpg_timeseries import (
+from brick_server.minimal.interfaces.timeseries import (
AsyncpgTimeseries as AsyncpgTimeseries,
-)
-from brick_server.minimal.interfaces.timeseries.base_timeseries import (
BaseTimeseries as BaseTimeseries,
+ InfluxDBTimeseries as InfluxDBTimeseries,
+ TimeseriesInterface as TimeseriesInterface,
)
diff --git a/brick_server/minimal/interfaces/actuation/__init__.py b/brick_server/minimal/interfaces/actuation/__init__.py
index e69de29..9c9916e 100644
--- a/brick_server/minimal/interfaces/actuation/__init__.py
+++ b/brick_server/minimal/interfaces/actuation/__init__.py
@@ -0,0 +1,5 @@
+from brick_server.minimal.interfaces.actuation.actuation_interface import (
+ ActuationInterface,
+)
+
+actuation_iface = ActuationInterface()
diff --git a/brick_server/minimal/interfaces/actuation/actuation_interface.py b/brick_server/minimal/interfaces/actuation/actuation_interface.py
new file mode 100644
index 0000000..e7633e1
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/actuation_interface.py
@@ -0,0 +1,62 @@
+import time
+
+from loguru import logger
+
+from brick_server.minimal.interfaces.actuation.bacnet import BacnetActuation
+from brick_server.minimal.interfaces.actuation.base_actuation import BaseActuation
+from brick_server.minimal.interfaces.actuation.metasys import MetasysActuation
+from brick_server.minimal.interfaces.cache import use_cache
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+from brick_server.minimal.utilities.utils import get_external_references
+
+
+class ActuationInterface:
+ def __init__(self):
+ self.actuation_dict = {
+ "https://brickschema.org/schema/Brick/ref#BACnetReference": BacnetActuation(),
+ "https://brickschema.org/schema/Brick/ref#MetasysReference": MetasysActuation(),
+ }
+
+ async def get_actuation_driver(self, external_references) -> BaseActuation:
+ types = external_references.getall(
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
+ )
+ for actuation_type in types:
+ if actuation_type in self.actuation_dict:
+ return self.actuation_dict[actuation_type]
+ raise BizError(ErrorCode.ActuationDriverNotFoundError, ",".join(types))
+
+ async def actuate(self, domain, entity_id, value):
+ # TODO: get actuation_name in brick graph with cache
+ start = time.time()
+ driver_time = 0
+ actuation_time = 0
+ try:
+ cache_key = f"{domain.name}:external_references:{entity_id}"
+ external_references = await use_cache(
+ cache_key, get_external_references, domain, entity_id
+ )
+ driver = await self.get_actuation_driver(external_references)
+ driver_time = time.time() - start # for benchmark
+ start = time.time()
+ success, detail = await driver.actuate(
+ entity_id, value, external_references
+ )
+ actuation_time = time.time() - start # for benchmark
+ except Exception as e:
+ success, detail = False, f"{e}"
+ return success, detail, driver_time, actuation_time
+
+ async def read(self, domain, entity_id):
+ # TODO: get actuation_name in brick graph with cache
+ try:
+ cache_key = f"{domain.name}:external_references:{entity_id}"
+ external_references = await use_cache(
+ cache_key, get_external_references, domain, entity_id
+ )
+ driver = await self.get_actuation_driver(external_references)
+ success, detail = await driver.read(entity_id, external_references)
+ except Exception as e:
+ success, detail = False, f"{e}"
+ logger.exception(e)
+ return success, detail
diff --git a/brick_server/minimal/interfaces/actuation/actuation_server_pb2.py b/brick_server/minimal/interfaces/actuation/actuation_server_pb2.py
deleted file mode 100644
index 90e07c6..0000000
--- a/brick_server/minimal/interfaces/actuation/actuation_server_pb2.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: actuation_server.proto
-"""Generated protocol buffer code."""
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import descriptor_pool as _descriptor_pool
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-
-
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x61\x63tuation_server.proto\x12\x0f\x61\x63tuationserver\"/\n\x0bSensorValue\x12\x11\n\tsensor_id\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"(\n\x06Status\x12\x0e\n\x06result\x18\x01 \x01(\x08\x12\x0e\n\x06\x64\x65tail\x18\x02 \x01(\t2\xb4\x01\n\x0f\x41\x63tuationServer\x12N\n\x13\x41\x63tuateSingleSensor\x12\x1c.actuationserver.SensorValue\x1a\x17.actuationserver.Status\"\x00\x12Q\n\x12\x41\x63tuateMultiSensor\x12\x1c.actuationserver.SensorValue\x1a\x17.actuationserver.Status\"\x00(\x01\x30\x01\x62\x06proto3')
-
-
-
-_SENSORVALUE = DESCRIPTOR.message_types_by_name['SensorValue']
-_STATUS = DESCRIPTOR.message_types_by_name['Status']
-SensorValue = _reflection.GeneratedProtocolMessageType('SensorValue', (_message.Message,), {
- 'DESCRIPTOR' : _SENSORVALUE,
- '__module__' : 'actuation_server_pb2'
- # @@protoc_insertion_point(class_scope:actuationserver.SensorValue)
- })
-_sym_db.RegisterMessage(SensorValue)
-
-Status = _reflection.GeneratedProtocolMessageType('Status', (_message.Message,), {
- 'DESCRIPTOR' : _STATUS,
- '__module__' : 'actuation_server_pb2'
- # @@protoc_insertion_point(class_scope:actuationserver.Status)
- })
-_sym_db.RegisterMessage(Status)
-
-_ACTUATIONSERVER = DESCRIPTOR.services_by_name['ActuationServer']
-if _descriptor._USE_C_DESCRIPTORS == False:
-
- DESCRIPTOR._options = None
- _SENSORVALUE._serialized_start=43
- _SENSORVALUE._serialized_end=90
- _STATUS._serialized_start=92
- _STATUS._serialized_end=132
- _ACTUATIONSERVER._serialized_start=135
- _ACTUATIONSERVER._serialized_end=315
-# @@protoc_insertion_point(module_scope)
diff --git a/brick_server/minimal/interfaces/actuation/actuation_server_pb2_grpc.py b/brick_server/minimal/interfaces/actuation/actuation_server_pb2_grpc.py
deleted file mode 100644
index 2fbd2ad..0000000
--- a/brick_server/minimal/interfaces/actuation/actuation_server_pb2_grpc.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-"""Client and server classes corresponding to protobuf-defined services."""
-import grpc
-
-from brick_server.minimal.interfaces.actuation import (
- actuation_server_pb2 as actuation__server__pb2,
-)
-
-
-class ActuationServerStub(object):
- """Missing associated documentation comment in .proto file."""
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.ActuateSingleSensor = channel.unary_unary(
- '/actuationserver.ActuationServer/ActuateSingleSensor',
- request_serializer=actuation__server__pb2.SensorValue.SerializeToString,
- response_deserializer=actuation__server__pb2.Status.FromString,
- )
- self.ActuateMultiSensor = channel.stream_stream(
- '/actuationserver.ActuationServer/ActuateMultiSensor',
- request_serializer=actuation__server__pb2.SensorValue.SerializeToString,
- response_deserializer=actuation__server__pb2.Status.FromString,
- )
-
-
-class ActuationServerServicer(object):
- """Missing associated documentation comment in .proto file."""
-
- def ActuateSingleSensor(self, request, context):
- """Missing associated documentation comment in .proto file."""
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def ActuateMultiSensor(self, request_iterator, context):
- """Missing associated documentation comment in .proto file."""
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ActuationServerServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'ActuateSingleSensor': grpc.unary_unary_rpc_method_handler(
- servicer.ActuateSingleSensor,
- request_deserializer=actuation__server__pb2.SensorValue.FromString,
- response_serializer=actuation__server__pb2.Status.SerializeToString,
- ),
- 'ActuateMultiSensor': grpc.stream_stream_rpc_method_handler(
- servicer.ActuateMultiSensor,
- request_deserializer=actuation__server__pb2.SensorValue.FromString,
- response_serializer=actuation__server__pb2.Status.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'actuationserver.ActuationServer', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
-
-
- # This class is part of an EXPERIMENTAL API.
-class ActuationServer(object):
- """Missing associated documentation comment in .proto file."""
-
- @staticmethod
- def ActuateSingleSensor(request,
- target,
- options=(),
- channel_credentials=None,
- call_credentials=None,
- insecure=False,
- compression=None,
- wait_for_ready=None,
- timeout=None,
- metadata=None):
- return grpc.experimental.unary_unary(request, target, '/actuationserver.ActuationServer/ActuateSingleSensor',
- actuation__server__pb2.SensorValue.SerializeToString,
- actuation__server__pb2.Status.FromString,
- options, channel_credentials,
- insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
-
- @staticmethod
- def ActuateMultiSensor(request_iterator,
- target,
- options=(),
- channel_credentials=None,
- call_credentials=None,
- insecure=False,
- compression=None,
- wait_for_ready=None,
- timeout=None,
- metadata=None):
- return grpc.experimental.stream_stream(request_iterator, target, '/actuationserver.ActuationServer/ActuateMultiSensor',
- actuation__server__pb2.SensorValue.SerializeToString,
- actuation__server__pb2.Status.FromString,
- options, channel_credentials,
- insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
diff --git a/brick_server/minimal/interfaces/actuation/bacnet/__init__.py b/brick_server/minimal/interfaces/actuation/bacnet/__init__.py
new file mode 100644
index 0000000..37a9673
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/bacnet/__init__.py
@@ -0,0 +1,29 @@
+import grpc
+from loguru import logger
+
+from brick_server.minimal.interfaces.actuation.bacnet import (
+ actuation_server_pb2,
+ actuation_server_pb2_grpc,
+)
+from brick_server.minimal.interfaces.actuation.base_actuation import BaseActuation
+
+
+class BacnetActuation(BaseActuation):
+ def __init__(self, *args, **kwargs):
+ pass
+
+ async def actuate(self, entity_id, value, external_references):
+ sensor_id = external_references[
+ "http://data.ashrae.org/bacnet/2020#object-identifier"
+ ]
+ logger.info("bacnet: {} {}", sensor_id, value)
+ async with grpc.aio.insecure_channel("137.110.160.254:50051") as channel:
+ stub = actuation_server_pb2_grpc.ActuationServerStub(channel)
+ data = actuation_server_pb2.SensorValue(sensor_id=sensor_id, value=value)
+ res = await stub.ActuateSingleSensor(data)
+ if not res.result:
+ print(res.detail)
+ return res.result, res.detail
+
+ async def read(self, entity_id, external_references):
+ raise NotImplementedError
diff --git a/brick_server/minimal/interfaces/actuation/bacnet/actuation_server_pb2.py b/brick_server/minimal/interfaces/actuation/bacnet/actuation_server_pb2.py
new file mode 100644
index 0000000..c1a838c
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/bacnet/actuation_server_pb2.py
@@ -0,0 +1,55 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: actuation_server.proto
+"""Generated protocol buffer code."""
+from google.protobuf import (
+ descriptor as _descriptor,
+ descriptor_pool as _descriptor_pool,
+ message as _message,
+ reflection as _reflection,
+ symbol_database as _symbol_database,
+)
+
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
+ b'\n\x16\x61\x63tuation_server.proto\x12\x0f\x61\x63tuationserver"/\n\x0bSensorValue\x12\x11\n\tsensor_id\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t"(\n\x06Status\x12\x0e\n\x06result\x18\x01 \x01(\x08\x12\x0e\n\x06\x64\x65tail\x18\x02 \x01(\t2\xb4\x01\n\x0f\x41\x63tuationServer\x12N\n\x13\x41\x63tuateSingleSensor\x12\x1c.actuationserver.SensorValue\x1a\x17.actuationserver.Status"\x00\x12Q\n\x12\x41\x63tuateMultiSensor\x12\x1c.actuationserver.SensorValue\x1a\x17.actuationserver.Status"\x00(\x01\x30\x01\x62\x06proto3'
+)
+
+
+_SENSORVALUE = DESCRIPTOR.message_types_by_name["SensorValue"]
+_STATUS = DESCRIPTOR.message_types_by_name["Status"]
+SensorValue = _reflection.GeneratedProtocolMessageType(
+ "SensorValue",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _SENSORVALUE,
+ "__module__": "actuation_server_pb2"
+ # @@protoc_insertion_point(class_scope:actuationserver.SensorValue)
+ },
+)
+_sym_db.RegisterMessage(SensorValue)
+
+Status = _reflection.GeneratedProtocolMessageType(
+ "Status",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _STATUS,
+ "__module__": "actuation_server_pb2"
+ # @@protoc_insertion_point(class_scope:actuationserver.Status)
+ },
+)
+_sym_db.RegisterMessage(Status)
+
+_ACTUATIONSERVER = DESCRIPTOR.services_by_name["ActuationServer"]
+if _descriptor._USE_C_DESCRIPTORS is False:
+ DESCRIPTOR._options = None
+ _SENSORVALUE._serialized_start = 43
+ _SENSORVALUE._serialized_end = 90
+ _STATUS._serialized_start = 92
+ _STATUS._serialized_end = 132
+ _ACTUATIONSERVER._serialized_start = 135
+ _ACTUATIONSERVER._serialized_end = 315
+# @@protoc_insertion_point(module_scope)
diff --git a/brick_server/minimal/interfaces/actuation/bacnet/actuation_server_pb2_grpc.py b/brick_server/minimal/interfaces/actuation/bacnet/actuation_server_pb2_grpc.py
new file mode 100644
index 0000000..0143ebb
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/bacnet/actuation_server_pb2_grpc.py
@@ -0,0 +1,126 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+
+from brick_server.minimal.interfaces.actuation.bacnet import (
+ actuation_server_pb2 as actuation__server__pb2,
+)
+
+
+class ActuationServerStub:
+ """Missing associated documentation comment in .proto file."""
+
+ def __init__(self, channel):
+ """Constructor.
+
+ Args:
+ channel: A grpc.Channel.
+ """
+ self.ActuateSingleSensor = channel.unary_unary(
+ "/actuationserver.ActuationServer/ActuateSingleSensor",
+ request_serializer=actuation__server__pb2.SensorValue.SerializeToString,
+ response_deserializer=actuation__server__pb2.Status.FromString,
+ )
+ self.ActuateMultiSensor = channel.stream_stream(
+ "/actuationserver.ActuationServer/ActuateMultiSensor",
+ request_serializer=actuation__server__pb2.SensorValue.SerializeToString,
+ response_deserializer=actuation__server__pb2.Status.FromString,
+ )
+
+
+class ActuationServerServicer:
+ """Missing associated documentation comment in .proto file."""
+
+ def ActuateSingleSensor(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details("Method not implemented!")
+ raise NotImplementedError("Method not implemented!")
+
+ def ActuateMultiSensor(self, request_iterator, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details("Method not implemented!")
+ raise NotImplementedError("Method not implemented!")
+
+
+def add_ActuationServerServicer_to_server(servicer, server):
+ rpc_method_handlers = {
+ "ActuateSingleSensor": grpc.unary_unary_rpc_method_handler(
+ servicer.ActuateSingleSensor,
+ request_deserializer=actuation__server__pb2.SensorValue.FromString,
+ response_serializer=actuation__server__pb2.Status.SerializeToString,
+ ),
+ "ActuateMultiSensor": grpc.stream_stream_rpc_method_handler(
+ servicer.ActuateMultiSensor,
+ request_deserializer=actuation__server__pb2.SensorValue.FromString,
+ response_serializer=actuation__server__pb2.Status.SerializeToString,
+ ),
+ }
+ generic_handler = grpc.method_handlers_generic_handler(
+ "actuationserver.ActuationServer", rpc_method_handlers
+ )
+ server.add_generic_rpc_handlers((generic_handler,))
+
+
+# This class is part of an EXPERIMENTAL API.
+class ActuationServer:
+ """Missing associated documentation comment in .proto file."""
+
+ @staticmethod
+ def ActuateSingleSensor(
+ request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None,
+ ):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ "/actuationserver.ActuationServer/ActuateSingleSensor",
+ actuation__server__pb2.SensorValue.SerializeToString,
+ actuation__server__pb2.Status.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ )
+
+ @staticmethod
+ def ActuateMultiSensor(
+ request_iterator,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None,
+ ):
+ return grpc.experimental.stream_stream(
+ request_iterator,
+ target,
+ "/actuationserver.ActuationServer/ActuateMultiSensor",
+ actuation__server__pb2.SensorValue.SerializeToString,
+ actuation__server__pb2.Status.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ )
diff --git a/brick_server/minimal/interfaces/actuation/base_actuation.py b/brick_server/minimal/interfaces/actuation/base_actuation.py
index 7627406..dd08b4d 100644
--- a/brick_server/minimal/interfaces/actuation/base_actuation.py
+++ b/brick_server/minimal/interfaces/actuation/base_actuation.py
@@ -1,8 +1,14 @@
-class BaseActuation(object):
- def __init__(self, *args, **kwargs):
- pass
+from abc import ABC, abstractmethod
- def actuate(self, entity_id, value):
+from multidict import MultiDict
+
+
+class BaseActuation(ABC):
+ # def __init__(self, *args, **kwargs):
+ # pass
+
+ @abstractmethod
+ async def actuate(self, entity_id: str, value, external_references: MultiDict):
"""Actuates an entity with a given value.
This function sets the current value of the entity with `entity_id` as `value` in "the actual system" such as BACnet devices. The value may have a physical impact in the real world.
@@ -10,6 +16,25 @@ def actuate(self, entity_id, value):
Args:
entity_id: The identifier of an entity.
value: A numeric value that the request wants to actuate of the entity.
+ external_references: External references from brick graph
+
+ Returns:
+ None. If the actuation is successful, the function completes. Otherwise, it raises an exception.
+
+ Raises:
+ TODO
+ """
+ raise NotImplementedError(
+ "This should be overriden by an actual implementation."
+ )
+
+ @abstractmethod
+ async def read(self, entity_id: str, external_references: MultiDict):
+ """Read the current value of an entity
+
+ Args:
+ entity_id: The identifier of an entity.
+ external_references: External references from brick graph
Returns:
None. If the actuation is successful, the function completes. Otherwise, it raises an exception.
diff --git a/brick_server/minimal/interfaces/actuation/dummy_actuation.py b/brick_server/minimal/interfaces/actuation/dummy_actuation.py
index ca27159..fe574e5 100644
--- a/brick_server/minimal/interfaces/actuation/dummy_actuation.py
+++ b/brick_server/minimal/interfaces/actuation/dummy_actuation.py
@@ -5,5 +5,5 @@ class DummyActuation(BaseActuation):
def __init__(self, *args, **kwargs):
pass
- def actuate(self, entity_id, value):
+ async def actuate(self, entity_id, value):
return True
diff --git a/brick_server/minimal/interfaces/actuation/metasys/__init__.py b/brick_server/minimal/interfaces/actuation/metasys/__init__.py
new file mode 100644
index 0000000..2378a9b
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/metasys/__init__.py
@@ -0,0 +1,43 @@
+import grpc
+from loguru import logger
+
+from brick_server.minimal.interfaces.actuation.base_actuation import BaseActuation
+from brick_server.minimal.interfaces.actuation.metasys import (
+ actuate_pb2,
+ actuate_pb2_grpc,
+)
+
+
+class MetasysActuation(BaseActuation):
+ def __init__(self, *args, **kwargs):
+ pass
+
+ async def actuate(self, entity_id, value, external_references):
+ sensor_id = external_references[
+ "https://brickschema.org/schema/Brick/ref#metasysID"
+ ]
+ logger.info("metasys: {} {}", sensor_id, value)
+ async with grpc.aio.insecure_channel("172.17.0.1:50051") as channel:
+ stub = actuate_pb2_grpc.ActuateStub(channel)
+ response: actuate_pb2.Response = await stub.TemporaryOverride(
+ actuate_pb2.TemporaryOverrideAction(
+ uuid=sensor_id, value=str(value), hour=4, minute=0
+ )
+ )
+ logger.info(response)
+ return response.status, response.details
+
+ async def read(self, entity_id, external_references):
+ sensor_id = external_references[
+ "https://brickschema.org/schema/Brick/ref#metasysID"
+ ]
+ logger.info("metasys read: {}", sensor_id)
+ async with grpc.aio.insecure_channel("172.17.0.1:50051") as channel:
+ stub = actuate_pb2_grpc.ActuateStub(channel)
+ response: actuate_pb2.Response = await stub.ReadObjectCurrent(
+ actuate_pb2.ReadObjectCurrentAction(
+ uuid=sensor_id, attribute="presentValue"
+ )
+ )
+ logger.info(response)
+ return response.status, response.details
diff --git a/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2.py b/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2.py
new file mode 100644
index 0000000..aa152b6
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2.py
@@ -0,0 +1,34 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: actuate.proto
+# Protobuf Python Version: 5.26.1
+"""Generated protocol buffer code."""
+from google.protobuf import (
+ descriptor as _descriptor,
+ descriptor_pool as _descriptor_pool,
+ symbol_database as _symbol_database,
+)
+from google.protobuf.internal import builder as _builder
+
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
+ b'\n\ractuate.proto":\n\x17ReadObjectCurrentAction\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x11\n\tattribute\x18\x02 \x01(\t"\x9a\x01\n\x17TemporaryOverrideAction\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12\x11\n\x04hour\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x13\n\x06minute\x18\x04 \x01(\rH\x01\x88\x01\x01\x12\x17\n\nannotation\x18\x05 \x01(\tH\x02\x88\x01\x01\x42\x07\n\x05_hourB\t\n\x07_minuteB\r\n\x0b_annotation"+\n\x08Response\x12\x0e\n\x06status\x18\x01 \x01(\x08\x12\x0f\n\x07\x64\x65tails\x18\x02 \x01(\t2\x81\x01\n\x07\x41\x63tuate\x12:\n\x11TemporaryOverride\x12\x18.TemporaryOverrideAction\x1a\t.Response"\x00\x12:\n\x11ReadObjectCurrent\x12\x18.ReadObjectCurrentAction\x1a\t.Response"\x00\x62\x06proto3'
+)
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "actuate_pb2", _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals["_READOBJECTCURRENTACTION"]._serialized_start = 17
+ _globals["_READOBJECTCURRENTACTION"]._serialized_end = 75
+ _globals["_TEMPORARYOVERRIDEACTION"]._serialized_start = 78
+ _globals["_TEMPORARYOVERRIDEACTION"]._serialized_end = 232
+ _globals["_RESPONSE"]._serialized_start = 234
+ _globals["_RESPONSE"]._serialized_end = 277
+ _globals["_ACTUATE"]._serialized_start = 280
+ _globals["_ACTUATE"]._serialized_end = 409
+# @@protoc_insertion_point(module_scope)
diff --git a/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2.pyi b/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2.pyi
new file mode 100644
index 0000000..3055846
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2.pyi
@@ -0,0 +1,44 @@
+from typing import ClassVar as _ClassVar, Optional as _Optional
+
+from google.protobuf import descriptor as _descriptor, message as _message
+
+DESCRIPTOR: _descriptor.FileDescriptor
+
+class ReadObjectCurrentAction(_message.Message):
+ __slots__ = ("uuid", "attribute")
+ UUID_FIELD_NUMBER: _ClassVar[int]
+ ATTRIBUTE_FIELD_NUMBER: _ClassVar[int]
+ uuid: str
+ attribute: str
+ def __init__(
+ self, uuid: _Optional[str] = ..., attribute: _Optional[str] = ...
+ ) -> None: ...
+
+class TemporaryOverrideAction(_message.Message):
+ __slots__ = ("uuid", "value", "hour", "minute", "annotation")
+ UUID_FIELD_NUMBER: _ClassVar[int]
+ VALUE_FIELD_NUMBER: _ClassVar[int]
+ HOUR_FIELD_NUMBER: _ClassVar[int]
+ MINUTE_FIELD_NUMBER: _ClassVar[int]
+ ANNOTATION_FIELD_NUMBER: _ClassVar[int]
+ uuid: str
+ value: str
+ hour: int
+ minute: int
+ annotation: str
+ def __init__(
+ self,
+ uuid: _Optional[str] = ...,
+ value: _Optional[str] = ...,
+ hour: _Optional[int] = ...,
+ minute: _Optional[int] = ...,
+ annotation: _Optional[str] = ...,
+ ) -> None: ...
+
+class Response(_message.Message):
+ __slots__ = ("status", "details")
+ STATUS_FIELD_NUMBER: _ClassVar[int]
+ DETAILS_FIELD_NUMBER: _ClassVar[int]
+ status: bool
+ details: str
+ def __init__(self, status: bool = ..., details: _Optional[str] = ...) -> None: ...
diff --git a/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2_grpc.py b/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2_grpc.py
new file mode 100644
index 0000000..c654ff4
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/metasys/actuate_pb2_grpc.py
@@ -0,0 +1,157 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import warnings
+
+import grpc
+
+import brick_server.minimal.interfaces.actuation.metasys.actuate_pb2 as actuate__pb2
+
+GRPC_GENERATED_VERSION = "1.63.0"
+GRPC_VERSION = grpc.__version__
+EXPECTED_ERROR_RELEASE = "1.65.0"
+SCHEDULED_RELEASE_DATE = "June 25, 2024"
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+
+ _version_not_supported = first_version_is_lower(
+ GRPC_VERSION, GRPC_GENERATED_VERSION
+ )
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ warnings.warn(
+ f"The grpc package installed is at version {GRPC_VERSION},"
+ + f" but the generated code in actuate_pb2_grpc.py depends on"
+ + f" grpcio>={GRPC_GENERATED_VERSION}."
+ + f" Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}"
+ + f" or downgrade your generated code using grpcio-tools<={GRPC_VERSION}."
+ + f" This warning will become an error in {EXPECTED_ERROR_RELEASE},"
+ + f" scheduled for release on {SCHEDULED_RELEASE_DATE}.",
+ RuntimeWarning,
+ )
+
+
+class ActuateStub:
+ """Missing associated documentation comment in .proto file."""
+
+ def __init__(self, channel):
+ """Constructor.
+
+ Args:
+ channel: A grpc.Channel.
+ """
+ self.TemporaryOverride = channel.unary_unary(
+ "/Actuate/TemporaryOverride",
+ request_serializer=actuate__pb2.TemporaryOverrideAction.SerializeToString,
+ response_deserializer=actuate__pb2.Response.FromString,
+ _registered_method=True,
+ )
+ self.ReadObjectCurrent = channel.unary_unary(
+ "/Actuate/ReadObjectCurrent",
+ request_serializer=actuate__pb2.ReadObjectCurrentAction.SerializeToString,
+ response_deserializer=actuate__pb2.Response.FromString,
+ _registered_method=True,
+ )
+
+
+class ActuateServicer:
+ """Missing associated documentation comment in .proto file."""
+
+ def TemporaryOverride(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details("Method not implemented!")
+ raise NotImplementedError("Method not implemented!")
+
+ def ReadObjectCurrent(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details("Method not implemented!")
+ raise NotImplementedError("Method not implemented!")
+
+
+def add_ActuateServicer_to_server(servicer, server):
+ rpc_method_handlers = {
+ "TemporaryOverride": grpc.unary_unary_rpc_method_handler(
+ servicer.TemporaryOverride,
+ request_deserializer=actuate__pb2.TemporaryOverrideAction.FromString,
+ response_serializer=actuate__pb2.Response.SerializeToString,
+ ),
+ "ReadObjectCurrent": grpc.unary_unary_rpc_method_handler(
+ servicer.ReadObjectCurrent,
+ request_deserializer=actuate__pb2.ReadObjectCurrentAction.FromString,
+ response_serializer=actuate__pb2.Response.SerializeToString,
+ ),
+ }
+ generic_handler = grpc.method_handlers_generic_handler(
+ "Actuate", rpc_method_handlers
+ )
+ server.add_generic_rpc_handlers((generic_handler,))
+
+
+# This class is part of an EXPERIMENTAL API.
+class Actuate:
+ """Missing associated documentation comment in .proto file."""
+
+ @staticmethod
+ def TemporaryOverride(
+ request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None,
+ ):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ "/Actuate/TemporaryOverride",
+ actuate__pb2.TemporaryOverrideAction.SerializeToString,
+ actuate__pb2.Response.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True,
+ )
+
+ @staticmethod
+ def ReadObjectCurrent(
+ request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None,
+ ):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ "/Actuate/ReadObjectCurrent",
+ actuate__pb2.ReadObjectCurrentAction.SerializeToString,
+ actuate__pb2.Response.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True,
+ )
diff --git a/brick_server/minimal/interfaces/actuation/metasys/test_actuator.py b/brick_server/minimal/interfaces/actuation/metasys/test_actuator.py
new file mode 100644
index 0000000..0d8784b
--- /dev/null
+++ b/brick_server/minimal/interfaces/actuation/metasys/test_actuator.py
@@ -0,0 +1,33 @@
+import actuate_pb2
+import actuate_pb2_grpc
+import grpc
+
+# with grpc.insecure_channel('localhost:50051') as channel:
+# stub = actuate_pb2_grpc.ActuateStub(channel)
+# response: actuate_pb2.Response = stub.TemporaryOverride(actuate_pb2.TemporaryOverrideAction(uuid='4d3ca6b4-562d-52cc-856b-1b3830e59005', value='binarypvEnumSet.bacbinInactive', hour=0, minute=2))
+# print(response.status, response.details)
+
+
+if __name__ == "__main__":
+ target = "02068f28-d239-5f24-9119-c36ef852c0fe" # cooling output
+ with grpc.insecure_channel("localhost:50051") as channel:
+ stub = actuate_pb2_grpc.ActuateStub(channel)
+
+ # response: actuate_pb2.Response = stub.TemporaryOverride(actuate_pb2.TemporaryOverrideAction(uuid=target, value='0', hour=4, minute=0))
+ # print(response.status, response.details)
+
+ # time.sleep(10*60)
+ # response: actuate_pb2.Response = stub.TemporaryOverride(actuate_pb2.TemporaryOverrideAction(uuid=target, value='40', hour=4, minute=0))
+ # print(response.status, response.details)
+
+ # time.sleep(20*60)
+ # response: actuate_pb2.Response = stub.TemporaryOverride(actuate_pb2.TemporaryOverrideAction(uuid=target, value='25', hour=4, minute=0))
+ # print(response.status, response.details)
+
+ # time.sleep(30*60)
+ response: actuate_pb2.Response = stub.TemporaryOverride(
+ actuate_pb2.TemporaryOverrideAction(
+ uuid=target, value="75", hour=4, minute=0
+ )
+ )
+ print(response.status, response.details)
diff --git a/brick_server/minimal/interfaces/actuation/real_actuation.py b/brick_server/minimal/interfaces/actuation/real_actuation.py
deleted file mode 100644
index 44a0d89..0000000
--- a/brick_server/minimal/interfaces/actuation/real_actuation.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import grpc
-
-from brick_server.minimal.interfaces.actuation import (
- actuation_server_pb2,
- actuation_server_pb2_grpc,
-)
-from brick_server.minimal.interfaces.actuation.base_actuation import BaseActuation
-
-
-class RealActuation(BaseActuation):
- def __init__(self, *args, **kwargs):
- pass
-
- def actuate(self, entity_id, value):
- with grpc.insecure_channel("137.110.160.254:50051") as channel:
- stub = actuation_server_pb2_grpc.ActuationServerStub(channel)
- data = actuation_server_pb2.SensorValue(sensor_id=entity_id, value=value)
- res = stub.ActuateSingleSensor(data)
- if not res.result:
- print(res.detail)
- return res.result, res.detail
- # pass
diff --git a/brick_server/minimal/interfaces/cache.py b/brick_server/minimal/interfaces/cache.py
new file mode 100644
index 0000000..07b681e
--- /dev/null
+++ b/brick_server/minimal/interfaces/cache.py
@@ -0,0 +1,59 @@
+import asyncio
+from typing import Any
+
+from aiocache import BaseCache, RedisCache, caches
+from loguru import logger
+
+from brick_server.minimal.config.manager import settings
+
+
+async def use_cache(key: str, fallback_func: Any, *args, **kwargs) -> Any:
+ cache: BaseCache = caches.get("default")
+ # print(settings.CACHE)
+ if settings.CACHE:
+ value = await cache.get(key)
+ else:
+ value = None
+ if value is None:
+ if asyncio.iscoroutinefunction(fallback_func):
+ value = await fallback_func(*args, **kwargs)
+ else:
+ value = fallback_func(*args, **kwargs)
+ if settings.CACHE:
+ await cache.set(key, value)
+ logger.debug("save cache {}: {}", key, value)
+ else:
+ logger.debug("load cache {}: {}", key, value)
+ return value
+
+
+async def clear_cache(key_prefix: str) -> None:
+ if not settings.CACHE:
+ return
+ cache: RedisCache = caches.get("default")
+ logger.debug("clear cache: {}", key_prefix)
+ keys = []
+ async for key in cache.client.scan_iter(f"{key_prefix}:*"):
+ keys.append(key)
+ jobs = [cache.delete(key) for key in keys]
+ await asyncio.gather(*jobs)
+ logger.debug("delete cache keys: {}", keys)
+
+
+caches.set_config(
+ {
+ "default": {
+ "cache": "aiocache.RedisCache",
+ "endpoint": settings.REDIS_HOST,
+ "port": settings.REDIS_PORT,
+ "db": settings.REDIS_DATABASE,
+ "password": settings.REDIS_PASSWORD,
+ "timeout": 1,
+ "serializer": {"class": "aiocache.serializers.PickleSerializer"},
+ "plugins": [
+ {"class": "aiocache.plugins.HitMissRatioPlugin"},
+ {"class": "aiocache.plugins.TimingPlugin"},
+ ],
+ }
+ }
+)
diff --git a/brick_server/minimal/interfaces/grafana.py b/brick_server/minimal/interfaces/grafana.py
deleted file mode 100644
index 7381586..0000000
--- a/brick_server/minimal/interfaces/grafana.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import requests
-
-
-class GrafanaEndpoint(object):
- def __init__(self, baseurl, apikey):
- self.baseurl = baseurl
- self.apikey = apikey
-
- async def get(self, path, **kwargs):
- headers = kwargs.get("headers", {})
- headers["Authorization"] = "Bearer " + self.apikey
- kwargs["headers"] = headers
- url = self.baseurl + path
- resp = requests.get(url, **kwargs)
- # async with aiohttp.ClientSession() as session:
- # async with session.post(url, **kwargs) as resp:
- # return resp
- return resp
-
- async def post(self, path, **kwargs):
- headers = kwargs.get("headers", {})
- headers["Authorization"] = "Bearer " + self.apikey
- kwargs["headers"] = headers
- url = self.baseurl + path
- resp = requests.post(url, **kwargs)
- # async with aiohttp.ClientSession() as session:
- # async with session.post(url, **kwargs) as resp:
- # return resp
- return resp
diff --git a/brick_server/minimal/interfaces/graphdb.py b/brick_server/minimal/interfaces/graphdb.py
index c23f736..591ef49 100644
--- a/brick_server/minimal/interfaces/graphdb.py
+++ b/brick_server/minimal/interfaces/graphdb.py
@@ -1,15 +1,21 @@
import ast
import json
+import re
from datetime import datetime
from pathlib import Path
-from typing import Optional
+from typing import Any, Optional
import httpx
from fastapi import UploadFile
from loguru import logger
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
class GraphDB:
+ prefix_regex = re.compile(r"^PREFIX (\w+): <(.+)>$")
+
def __init__(self, host: str, port: int, repository: str) -> None:
self.host = host
self.port = port
@@ -31,7 +37,7 @@ async def init_repository(self, repository):
}
},
}
- resp = await self.client.post("/rest/repositories", json=data)
+ resp = await self.client.post("/rest/repositories", json=data, timeout=30)
if resp.status_code != 201:
logger.debug(resp.content)
# resp = await self.client.get(f"/rest/repositories/{self.repository}/size")
@@ -79,6 +85,7 @@ async def list_graphs(self, repository):
resp = await self.client.get(
f"/repositories/{repository}/contexts",
headers={"Accept": "application/json"},
+ timeout=30,
)
try:
data = resp.json()
@@ -158,14 +165,46 @@ async def import_schema_from_file(
)
logger.debug(resp.content)
+ def parse_prefix(self, query_str: str) -> dict[str, str]:
+ result = {}
+ for line in query_str.split("\n"):
+ match = self.prefix_regex.fullmatch(line)
+ if match is None:
+ break
+ prefix, full_url = match.groups()
+ result[full_url] = prefix
+ # logger.info(result)
+ return result
+
+ @staticmethod
+ def parse_entity_with_prefixes(value: str, prefixes: dict[str, str]) -> str:
+ for full_url, prefix in prefixes.items():
+ if value.startswith(full_url):
+ return f"{prefix}:{value[len(full_url):]}"
+ return value
+
+ @staticmethod
+ def parse_result(
+ result: dict[str, Any], prefixes: dict[str, str]
+ ) -> dict[str, Any]:
+ keys = result["head"]["vars"]
+ d = {key: [] for key in keys}
+ for row in result["results"]["bindings"]:
+ for i, key in enumerate(keys):
+ value = row[key]["value"]
+ value = GraphDB.parse_entity_with_prefixes(value, prefixes)
+ d[key].append(value)
+ return d
+
async def query(
self,
repository: str,
query_str: str,
- is_update=False,
- limit: int = 1000,
+ is_update: bool = False,
+ limit: int = 10000,
offset: int = 0,
- ):
+ parse_prefix: bool = True,
+ ) -> tuple[dict[str, Any], dict[str, str]]:
resp = await self.client.post(
"/rest/sparql/addKnownPrefixes",
data=query_str,
@@ -175,7 +214,7 @@ async def query(
)
query_str = resp.content.decode("utf-8")
query_str = ast.literal_eval(query_str)
- logger.debug(query_str)
+ # logger.debug(query_str)
data = {
"query": query_str,
"infer": True,
@@ -194,6 +233,25 @@ async def query(
resp = await self.client.post(
f"/repositories/{repository}", data=data, headers=headers
)
+ if resp.status_code != 200:
+ raise BizError(
+ ErrorCode.GraphDBError,
+ resp.content.decode("utf-8"),
+ )
result = resp.json()
- logger.debug(result)
- return result
+ if parse_prefix:
+ prefixes = self.parse_prefix(query_str)
+ else:
+ prefixes = {}
+ return result, prefixes
+ # logger.debug(result)
+ # parsed_result = self.parse_result(result, prefixes)
+ # logger.debug(parsed_result)
+ # return parsed_result
+
+
+graphdb = GraphDB(
+ host=settings.GRAPHDB_HOST,
+ port=settings.GRAPHDB_PORT,
+ repository=settings.GRAPHDB_REPOSITORY,
+)
diff --git a/brick_server/minimal/interfaces/mongodb.py b/brick_server/minimal/interfaces/mongodb.py
new file mode 100644
index 0000000..c2b31f5
--- /dev/null
+++ b/brick_server/minimal/interfaces/mongodb.py
@@ -0,0 +1,53 @@
+from typing import Type
+
+import fastapi
+import pydantic
+from beanie import Document, init_beanie
+from loguru import logger
+from motor.motor_asyncio import (
+ AsyncIOMotorClient,
+ AsyncIOMotorDatabase,
+ AsyncIOMotorGridFSBucket,
+)
+
+from brick_server.minimal.config.manager import settings
+
+
+class AsyncDatabase:
+ def __init__(self):
+ self.mongo_uri: pydantic.MongoDsn = pydantic.MongoDsn(
+ url=f"{settings.MONGO_SCHEMA}://{settings.MONGO_USERNAME}:{settings.MONGO_PASSWORD}@{settings.MONGO_HOST}:{settings.MONGO_PORT}",
+ )
+ self.async_client: AsyncIOMotorClient = AsyncIOMotorClient(
+ str(self.mongo_uri), uuidRepresentation="standard"
+ )
+ self.async_database: AsyncIOMotorDatabase = self.async_client[
+ settings.MONGO_DATABASE
+ ]
+ self.gridfs_bucket: AsyncIOMotorGridFSBucket = AsyncIOMotorGridFSBucket(
+ database=self.async_database,
+ bucket_name="fs",
+ )
+
+
+async_db: AsyncDatabase = AsyncDatabase()
+
+
+async def initialize_mongodb(
+ backend_app: fastapi.FastAPI, document_models: list[Type[Document]]
+) -> None:
+ logger.info("Database Connection --- Establishing . . .")
+
+ backend_app.state.db = async_db
+
+ server_info = await async_db.async_client.server_info()
+ logger.info("Database Version: MongoDB v{}", server_info["version"])
+ await init_beanie(
+ database=async_db.async_database,
+ document_models=document_models,
+ )
+ logger.info(
+ "Init collections: {}",
+ ", ".join([model.get_settings().name for model in document_models]),
+ )
+ logger.info("Database Connection --- Successfully Established!")
diff --git a/brick_server/minimal/interfaces/namespaces.py b/brick_server/minimal/interfaces/namespaces.py
deleted file mode 100644
index 97d7f9d..0000000
--- a/brick_server/minimal/interfaces/namespaces.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from rdflib import Namespace
-
-URN = Namespace("urn:")
-UUID = Namespace("urn:uuid:")
diff --git a/brick_server/minimal/interfaces/timeseries/__init__.py b/brick_server/minimal/interfaces/timeseries/__init__.py
index e69de29..a8c3315 100644
--- a/brick_server/minimal/interfaces/timeseries/__init__.py
+++ b/brick_server/minimal/interfaces/timeseries/__init__.py
@@ -0,0 +1,38 @@
+from loguru import logger
+
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.interfaces.timeseries.asyncpg import AsyncpgTimeseries
+from brick_server.minimal.interfaces.timeseries.base_timeseries import BaseTimeseries
+from brick_server.minimal.interfaces.timeseries.influxdb import InfluxDBTimeseries
+from brick_server.minimal.interfaces.timeseries.timeseries_interface import (
+ TimeseriesInterface,
+)
+
+ts_db = AsyncpgTimeseries(
+ settings.TIMESCALE_DATABASE,
+ settings.TIMESCALE_USERNAME,
+ settings.TIMESCALE_PASSWORD,
+ settings.TIMESCALE_HOST,
+ settings.TIMESCALE_PORT,
+)
+
+influx_db = InfluxDBTimeseries(
+ settings.INFLUXDB_URL,
+ settings.INFLUXDB_TOKEN,
+ settings.INFLUXDB_ORG,
+ settings.INFLUXDB_BUCKET,
+)
+
+timeseries_iface = TimeseriesInterface()
+
+
+async def initialize_timeseries():
+ logger.info("Init timescale tables")
+ await ts_db.init()
+ logger.info("Init InfluxDB")
+ await influx_db.init()
+
+
+async def dispose_timeseries():
+ logger.info("Dispose timescale connection")
+ await ts_db.dispose()
diff --git a/brick_server/minimal/interfaces/timeseries/asyncpg_timeseries.py b/brick_server/minimal/interfaces/timeseries/asyncpg/__init__.py
similarity index 76%
rename from brick_server/minimal/interfaces/timeseries/asyncpg_timeseries.py
rename to brick_server/minimal/interfaces/timeseries/asyncpg/__init__.py
index a9cf293..06b8450 100644
--- a/brick_server/minimal/interfaces/timeseries/asyncpg_timeseries.py
+++ b/brick_server/minimal/interfaces/timeseries/asyncpg/__init__.py
@@ -3,7 +3,7 @@
import aiofiles
import asyncpg
-import pandas as pd
+from asyncpg import Pool
from loguru import logger
from shapely.geometry import Point
@@ -43,6 +43,7 @@ def __init__(
):
self.DB_NAME = dbname
self.TABLE_NAME_PREFIX = "brick_data"
+ self.HISTORY_TABLE_NAME_PREFIX = "brick_history"
self.conn_str = f"postgres://{user}:{pw}@{host}:{port}/{dbname}"
self.value_cols = ["number", "text", "loc"]
self.pagination_size = 500
@@ -53,21 +54,30 @@ def __init__(
if not write_blob:
self.write_blob = self.write_blob_fs
+ self.column_type_map = {
+ "time": "TIMESTAMP",
+ "number": "DOUBLE PRECISION",
+ "text": "TEXT",
+ "loc": "geometry(Point,4326)",
+ }
+ self.pool: Pool | None = None
+
async def init(self, **pool_config):
self.pool = await asyncpg.create_pool(dsn=self.conn_str, **pool_config)
# await self._init_table()
logger.info("Timeseries Initialized")
+ async def dispose(self):
+ if self.pool:
+ await self.pool.close()
+
def get_table_name(self, domain_name):
return f"{self.TABLE_NAME_PREFIX}_{domain_name}"
+ def get_history_table_name(self, domain_name):
+ return f"{self.HISTORY_TABLE_NAME_PREFIX}_{domain_name}"
+
async def init_table(self, domain_name):
- self.column_type_map = {
- "time": "TIMESTAMP",
- "number": "DOUBLE PRECISION",
- "text": "TEXT",
- "loc": "geometry(Point,4326)",
- }
table_name = self.get_table_name(domain_name)
qstrs = [
"""
@@ -92,12 +102,58 @@ async def init_table(self, domain_name):
table_name=table_name
),
"""
- CREATE INDEX IF NOT EXISTS brick_data_time_index ON {table_name} (time DESC);
+ CREATE INDEX IF NOT EXISTS {table_name}_time_index ON {table_name} (time DESC);
""".format(
table_name=table_name
),
"""
- CREATE INDEX IF NOT EXISTS brick_data_uuid_index ON {table_name} (uuid);
+ CREATE INDEX IF NOT EXISTS {table_name}_uuid_index ON {table_name} (uuid);
+ """.format(
+ table_name=table_name
+ ),
+ ]
+ async with self.pool.acquire() as conn:
+ for qstr in qstrs:
+ try:
+ res = await conn.execute(qstr)
+ except Exception as e:
+ if "already a hypertable" in str(e):
+ pass
+ else:
+ raise e
+ logger.info("Init table {}", table_name)
+
+ async def init_history_table(self, domain_name):
+ table_name = self.get_history_table_name(domain_name)
+ qstrs = [
+ """
+ CREATE TABLE IF NOT EXISTS {table_name} (
+ uuid TEXT NOT NULL,
+ user_id TEXT NOT NULL,
+ app_name TEXT NOT NULL,
+ domain_user_app TEXT NOT NULL,
+ time TIMESTAMP NOT NULL,
+ value TEXT NOT NULL DEFAULT '',
+ PRIMARY KEY (uuid, time)
+ );
+ """.format(
+ table_name=table_name
+ ),
+ """
+ CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
+ """,
+ """
+ SELECT create_hypertable('{table_name}', 'time');
+ """.format(
+ table_name=table_name
+ ),
+ """
+ CREATE INDEX IF NOT EXISTS {table_name}_time_index ON {table_name} (time DESC);
+ """.format(
+ table_name=table_name
+ ),
+ """
+ CREATE INDEX IF NOT EXISTS {table_name}_uuid_index ON {table_name} (uuid);
""".format(
table_name=table_name
),
@@ -139,10 +195,13 @@ def display_data(self, res):
numbers.append(number)
texts.append(text)
locs.append(loc)
- df = pd.DataFrame(
- {"time": times, "uuid": uuids, "number": numbers, "loc": locs}
+ logger.info(
+ "time: {}, uuid: {}, number: {}, loc: {}", times, uuids, numbers, locs
)
- print(df)
+ # df = pd.DataFrame(
+ # {"time": times, "uuid": uuids, "number": numbers, "loc": locs}
+ # )
+ # print(df)
def serialize_records(self, records):
return [tuple(row) for row in records]
@@ -230,6 +289,8 @@ async def query(
uuids=[],
start_time=None,
end_time=None,
+ limit=-1,
+ offset=0,
value_types=["number"],
):
# qstr = """
@@ -253,6 +314,9 @@ async def query(
if uuids:
qstr += "uuid IN ({})\n AND ".format("'" + "', '".join(uuids) + "'")
qstr = qstr[:-4]
+ qstr += "OFFSET {}\n".format(offset)
+ if limit > 0:
+ qstr += "LIMIT {}\n".format(limit)
return await self._fetch(qstr)
# TODO: Unify encode & add_data over different data types.
@@ -335,6 +399,26 @@ async def add_data(self, domain_name, data, data_type="number"):
elif data_type == "text":
await self._add_text_data(domain_name, data)
+ async def add_history_data(
+ self, domain_name, entity_id, user_id, app_name, domain_user_app, time, value
+ ):
+ table_name = self.get_history_table_name(domain_name)
+ async with self.pool.acquire() as conn:
+ res = await conn.execute(
+ f"""INSERT INTO {table_name} (uuid, user_id, app_name, domain_user_app, time, value)
+ VALUES ('{entity_id}', '{user_id}', '{app_name}', '{domain_user_app}', '{time}', '{value}');"""
+ )
+
+ async def get_history_data(self, domain_name, entity_ids):
+ table_name = self.get_history_table_name(domain_name)
+ entity_ids_string = ",".join(map(lambda x: f"'{x}'", entity_ids))
+ query = f"""SELECT (uuid, user_id, app_name, domain_user_app, time, value) FROM {table_name} WHERE uuid IN ({entity_ids_string});"""
+ logger.info(query)
+ async with self.pool.acquire() as conn:
+ res = [record["row"] for record in await conn.fetch(query)]
+ logger.info(res)
+ return res
+
def main():
dbname = "brick"
diff --git a/brick_server/minimal/interfaces/timeseries/base_timeseries.py b/brick_server/minimal/interfaces/timeseries/base_timeseries.py
index 2f2ce6a..a250275 100644
--- a/brick_server/minimal/interfaces/timeseries/base_timeseries.py
+++ b/brick_server/minimal/interfaces/timeseries/base_timeseries.py
@@ -1,8 +1,8 @@
-class BaseTimeseries(object):
+class BaseTimeseries:
# def __init__(self, *args, **kwargs):
# pass
- def add_data(self, domain_name, data):
+ async def add_data(self, domain_name, data):
"""Adds timeseries data
This function adds data into the designated timeseries database.
@@ -20,15 +20,18 @@ def add_data(self, domain_name, data):
"This should be overriden by an actual implementation."
)
- def query(self, domain_name, start_time, end_time, entity_ids):
+ async def query(self, domain, entity_ids, start_time, end_time, limit=0, offset=0):
"""Executes a basic query over the timeseries DB.
Processes a basic range-based query over the timeseries DB.
Args:
+ domain:
start_time: UNIX timestamp in seconds as a floating point.
end_time: UNIX timestamp in seconds as a floating point.
entity_ids: A list of string entity identifiers.
+ limit:
+ offset:
Returns:
@@ -37,7 +40,7 @@ def query(self, domain_name, start_time, end_time, entity_ids):
"This should be overriden by an actual implementation."
)
- def raw_query(self, qstr):
+ async def raw_query(self, qstr):
"""Executes SQL query over the timeseries DB.
Executes SQL query over timeseries data.
@@ -51,3 +54,10 @@ def raw_query(self, qstr):
raise NotImplementedError(
"This should be overriden by an actual implementation."
)
+
+ async def add_history_data(
+ self, domain_name, entity_id, user_id, app_name, domain_user_app, time, value
+ ):
+ raise NotImplementedError(
+ "This should be overriden by an actual implementation."
+ )
diff --git a/brick_server/minimal/interfaces/timeseries/influxdb/__init__.py b/brick_server/minimal/interfaces/timeseries/influxdb/__init__.py
new file mode 100644
index 0000000..4baae66
--- /dev/null
+++ b/brick_server/minimal/interfaces/timeseries/influxdb/__init__.py
@@ -0,0 +1,52 @@
+from datetime import datetime
+
+import arrow
+from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync
+from loguru import logger
+
+from brick_server.minimal.interfaces.timeseries.base_timeseries import BaseTimeseries
+
+
+class InfluxDBTimeseries(BaseTimeseries):
+ def __init__(self, url, token, org, bucket):
+ self.url = url
+ self.token = token
+ self.org = org
+ self.bucket = bucket
+ self.client = None
+
+ async def init(self):
+ self.client = InfluxDBClientAsync(
+ url=self.url, token=self.token, org=self.org, timeout=100000
+ )
+ ready = await self.client.ping()
+ logger.info("InfluxDB Initialized: {}", ready)
+
+ async def query(self, domain, entity_ids, start_time, end_time, limit=0, offset=0):
+ query_api = self.client.query_api()
+ # |> range(start: -10m)
+ qstr = f'from(bucket:"{self.bucket}")'
+ if start_time:
+ start = (
+ arrow.get(start_time).to("utc").format("YYYY-MM-DDTHH:mm:ss.SSS") + "Z"
+ )
+ else:
+ start = "-10d"
+
+ if end_time:
+ qstr += f"\n|> range(start: {start}, stop: {arrow.get(end_time).to('utc').format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z'})"
+ else:
+ qstr += f"\n|> range(start: {start})"
+
+ if limit > 0:
+ qstr += f"\n|> limit(n: {limit}, offset: {offset})"
+ qstr += '\n|> sort(columns: ["_time"], desc: true)'
+ qstr += f'\n|> filter(fn: (r) => r._measurement == "heattransfer-test1" and r.id == "{entity_ids[0]}")'
+ logger.info(qstr)
+
+ records = await query_api.query_stream(qstr)
+ data = []
+ async for record in records:
+ data.append([record["id"], record["_time"], record["_value"]])
+ # print(record)
+ return data
diff --git a/brick_server/minimal/interfaces/timeseries/timeseries_interface.py b/brick_server/minimal/interfaces/timeseries/timeseries_interface.py
new file mode 100644
index 0000000..4b3055b
--- /dev/null
+++ b/brick_server/minimal/interfaces/timeseries/timeseries_interface.py
@@ -0,0 +1,50 @@
+from urllib.parse import urlparse
+
+from loguru import logger
+
+from brick_server.minimal.interfaces.timeseries.base_timeseries import BaseTimeseries
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+from brick_server.minimal.utilities.utils import get_external_references
+
+
+class TimeseriesInterface:
+ def __init__(self):
+ from brick_server.minimal.interfaces.timeseries import influx_db, ts_db
+
+ self.db_dict = {
+ "psql": ts_db,
+ "postgres": ts_db,
+ "postgresql": ts_db,
+ "influxdb": influx_db,
+ }
+
+ async def get_timeseries_driver(self, external_references) -> BaseTimeseries:
+ types = external_references.getall(
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
+ )
+ if "https://brickschema.org/schema/Brick/ref#TimeseriesReference" not in types:
+ raise BizError(ErrorCode.TimeseriesDriverNotFoundError, ",".join(types))
+
+ stored_at = external_references.get(
+ "https://brickschema.org/schema/Brick/ref#storedAt"
+ )
+ # logger.info(stored_at)
+ url = urlparse(stored_at)
+ if url.scheme in self.db_dict:
+ return self.db_dict[url.scheme]
+
+ raise BizError(ErrorCode.TimeseriesDriverNotFoundError, url.scheme)
+
+ async def query(self, domain, entity_ids, start_time, end_time, limit=0, offset=0):
+ # TODO: support multiple entity ids
+ entity_id = entity_ids[0]
+ # TODO: get timeseries reference only
+ external_references = await get_external_references(domain, entity_id)
+ logger.info(external_references)
+ driver = await self.get_timeseries_driver(external_references)
+ timeseries_id = external_references.get(
+ "https://brickschema.org/schema/Brick/ref#hasTimeseriesId"
+ )
+ return await driver.query(
+ domain.name, [timeseries_id], start_time, end_time, limit, offset
+ )
diff --git a/brick_server/minimal/models/__init__.py b/brick_server/minimal/models/__init__.py
new file mode 100644
index 0000000..3e4e092
--- /dev/null
+++ b/brick_server/minimal/models/__init__.py
@@ -0,0 +1,4 @@
+# nopycln: file
+
+from brick_server.minimal.models.domain import Domain as Domain
+from brick_server.minimal.models.user import User as User
diff --git a/brick_server/minimal/models/domain.py b/brick_server/minimal/models/domain.py
new file mode 100644
index 0000000..e148778
--- /dev/null
+++ b/brick_server/minimal/models/domain.py
@@ -0,0 +1,9 @@
+from beanie import Document, Indexed
+
+
+class Domain(Document):
+ name: Indexed(str, unique=True)
+ initialized: bool = False
+
+ class Settings:
+ name = "domains"
diff --git a/brick_server/minimal/models/user.py b/brick_server/minimal/models/user.py
new file mode 100644
index 0000000..6fb031c
--- /dev/null
+++ b/brick_server/minimal/models/user.py
@@ -0,0 +1,22 @@
+from beanie import Document
+from fastapi_users.db import BeanieBaseUser
+from pydantic import Field
+from pymongo import IndexModel
+from pymongo.collation import Collation
+
+from brick_server.minimal.schemas.oauth_account import OAuthAccount
+
+
+class User(BeanieBaseUser, Document):
+ name: str
+ oauth_accounts: list[OAuthAccount] = Field(default_factory=list)
+
+ class Settings(BeanieBaseUser.Settings):
+ name = "users"
+ name_collation = Collation("en", strength=2)
+ indexes = [
+ IndexModel("name", unique=True),
+ IndexModel(
+ "name", name="case_insensitive_name_index", collation=name_collation
+ ),
+ ]
diff --git a/brick_server/minimal/openapi.py b/brick_server/minimal/openapi.py
deleted file mode 100644
index e5c687f..0000000
--- a/brick_server/minimal/openapi.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""
-Generate the openapi schema
-"""
-
-import json
-from pathlib import Path
-from typing import Optional
-
-import click
-
-from brick_server.minimal.app import app
-
-
-@click.command("openapi")
-@click.option("-o", "--output", type=click.Path(), required=False, default=None)
-def main(output: Optional[str]) -> None:
- openapi_json = json.dumps(app.openapi(), indent=2)
- if output is None:
- print(openapi_json)
- else:
- with Path(output).open("w", encoding="utf-8") as f:
- f.write(openapi_json)
-
-
-if __name__ == "__main__":
- main() # pylint: disable=no-value-for-parameter
diff --git a/brick_server/minimal/schemas/__init__.py b/brick_server/minimal/schemas/__init__.py
new file mode 100644
index 0000000..8a82905
--- /dev/null
+++ b/brick_server/minimal/schemas/__init__.py
@@ -0,0 +1,22 @@
+# nopycln: file
+
+from brick_server.minimal.schemas.actuation import ActuationResult, ActuationResults
+from brick_server.minimal.schemas.base import (
+ Empty,
+ PaginationQuery,
+ StandardListResponse,
+ StandardResponse,
+)
+from brick_server.minimal.schemas.domain import DomainCreate, DomainRead, DomainUpdate
+from brick_server.minimal.schemas.entity import EntityRead
+from brick_server.minimal.schemas.permission import PermissionScope, PermissionType
+from brick_server.minimal.schemas.timeseries import (
+ ColumnType,
+ TimeseriesData,
+ ValueType,
+)
+from brick_server.minimal.schemas.user import (
+ UserCreate as UserCreate,
+ UserRead as UserRead,
+ UserUpdate as UserUpdate,
+)
diff --git a/brick_server/minimal/schemas/actuation.py b/brick_server/minimal/schemas/actuation.py
new file mode 100644
index 0000000..521a6e6
--- /dev/null
+++ b/brick_server/minimal/schemas/actuation.py
@@ -0,0 +1,11 @@
+from brick_server.minimal.schemas.base import BaseModel
+
+
+class ActuationResult(BaseModel):
+ success: bool
+ detail: str = ""
+
+
+class ActuationResults(BaseModel):
+ results: list[ActuationResult] = []
+ response_time: dict = {}
diff --git a/brick_server/minimal/schemas/base.py b/brick_server/minimal/schemas/base.py
new file mode 100644
index 0000000..d345c39
--- /dev/null
+++ b/brick_server/minimal/schemas/base.py
@@ -0,0 +1,140 @@
+from enum import Enum
+from functools import lru_cache
+from typing import Annotated, Any, Generic, List, Optional, Tuple, Type, TypeVar, Union
+
+from beanie import Document, PydanticObjectId
+from humps import camelize
+from pydantic import BaseModel as PydanticBaseModel, ConfigDict, Field, create_model
+
+from brick_server.minimal.utilities.exceptions import ErrorCode, ErrorShowType
+
+BT = TypeVar("BT", bound=PydanticBaseModel)
+
+
+@lru_cache(maxsize=128)
+def get_standard_list_response_sub_model(
+ cls: Type[PydanticBaseModel],
+) -> Type[PydanticBaseModel]:
+ name = cls.__name__
+ return create_model(
+ f"{name}List",
+ count=(int, 0),
+ results=(List[cls], []), # type: ignore
+ )
+
+
+@lru_cache(maxsize=256)
+def get_standard_response_model(
+ cls: Type[PydanticBaseModel], is_list: bool = False
+) -> Tuple[Type[PydanticBaseModel], Optional[Type[PydanticBaseModel]]]:
+ name = cls.__name__
+ sub_model: Optional[Type[PydanticBaseModel]]
+ if is_list:
+ model_name = f"{name}ListResp"
+ sub_model = get_standard_list_response_sub_model(cls)
+ data_type = (Optional[sub_model], None)
+ else:
+ model_name = f"{name}Resp"
+ sub_model = None
+ data_type = (Optional[cls], None)
+
+ config = ConfigDict(
+ alias_generator=camelize,
+ populate_by_name=True,
+ )
+
+ model = create_model(
+ model_name,
+ error_code=(ErrorCode, ...),
+ error_message=(str, ""),
+ show_type=(ErrorShowType, ErrorShowType.Silent),
+ data=data_type,
+ __config__=config,
+ )
+
+ return model, sub_model
+
+
+class Empty(PydanticBaseModel):
+ pass
+
+
+class StandardResponse(Generic[BT]):
+ def __class_getitem__(cls, item: Any) -> Type[Any]:
+ return get_standard_response_model(item)[0]
+
+ def __new__(cls, data: Union[BT, Empty] = Empty()) -> "StandardResponse[BT]":
+ response_type, _ = get_standard_response_model(type(data)) # type: ignore
+ response_data = data
+
+ return response_type( # type: ignore
+ error_code=ErrorCode.Success,
+ error_message="",
+ show_type=ErrorShowType.Silent,
+ data=response_data,
+ )
+
+
+class StandardListResponse(Generic[BT]):
+ def __class_getitem__(cls, item: Any) -> Type[Any]:
+ return get_standard_response_model(item, True)[0]
+
+ def __new__(
+ cls,
+ results: Optional[List[BT]] = None,
+ count: Optional[int] = None,
+ ) -> "StandardListResponse[BT]":
+ if results is None:
+ results = []
+ data_type = len(results) and type(results[0]) or Empty
+ response_type, sub_model_type = get_standard_response_model(data_type, True) # type: ignore
+ if count is None:
+ count = len(results)
+ response_data: PydanticBaseModel
+ if sub_model_type is None:
+ response_data = Empty()
+ else:
+ response_data = sub_model_type(count=count, results=results)
+
+ return response_type( # type: ignore
+ error_code=ErrorCode.Success,
+ error_message="",
+ show_type=ErrorShowType.Silent,
+ data=response_data,
+ )
+
+
+class BaseModel(PydanticBaseModel):
+ class Config:
+ # reference: fastapi-camelcase
+ alias_generator = camelize
+ # The name of this configuration setting was changed in pydantic v2 from
+ # `allow_population_by_alias` to `populate_by_name`.
+ populate_by_name = True
+ validate_default = True
+
+ def to_response(self: BT) -> StandardResponse[BT]:
+ return StandardResponse(self)
+
+ def update_model(self: BT, model: Document) -> None:
+ for k, v in self.dict().items():
+ if v is not None:
+ setattr(model, k, v)
+
+
+PaginationLimit = 500
+
+
+class PaginationQuery(BaseModel):
+ offset: Annotated[int, Field(ge=0)]
+ limit: Annotated[int, Field(gt=0, le=PaginationLimit)]
+
+
+class DBRef(BaseModel):
+ id: PydanticObjectId
+ # collection: str | None = None
+
+
+class StrEnumMixin(str, Enum):
+ def __str__(self) -> str:
+ return self.value
diff --git a/brick_server/minimal/schemas/domain.py b/brick_server/minimal/schemas/domain.py
new file mode 100644
index 0000000..b20c868
--- /dev/null
+++ b/brick_server/minimal/schemas/domain.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from beanie import PydanticObjectId
+
+from brick_server.minimal.schemas.base import BaseModel
+
+
+class DomainRead(BaseModel):
+ id: PydanticObjectId
+ name: str
+ initialized: bool
+
+
+class DomainCreate(BaseModel):
+ name: str
+
+
+class DomainUpdate(BaseModel):
+ name: Optional[str] = None
diff --git a/brick_server/minimal/schemas/entity.py b/brick_server/minimal/schemas/entity.py
new file mode 100644
index 0000000..5a96e15
--- /dev/null
+++ b/brick_server/minimal/schemas/entity.py
@@ -0,0 +1,15 @@
+from copy import deepcopy
+
+from pydantic import conlist
+
+from brick_server.minimal.schemas.base import BaseModel
+
+TupleModel = conlist(str, min_length=2, max_length=2)
+Relationship = deepcopy(TupleModel)
+
+
+class EntityRead(BaseModel):
+ relationships: list[Relationship] = []
+ types: list[str]
+ entity_id: str
+ name: str = ""
diff --git a/brick_server/minimal/schemas/oauth_account.py b/brick_server/minimal/schemas/oauth_account.py
new file mode 100644
index 0000000..7888916
--- /dev/null
+++ b/brick_server/minimal/schemas/oauth_account.py
@@ -0,0 +1,5 @@
+from fastapi_users.db import BaseOAuthAccount
+
+
+class OAuthAccount(BaseOAuthAccount):
+ pass
diff --git a/brick_server/minimal/schemas/permission.py b/brick_server/minimal/schemas/permission.py
new file mode 100644
index 0000000..e471a27
--- /dev/null
+++ b/brick_server/minimal/schemas/permission.py
@@ -0,0 +1,18 @@
+from enum import Enum
+
+from brick_server.minimal.schemas.base import StrEnumMixin
+
+
+class PermissionType(StrEnumMixin, Enum):
+ READ = "read"
+ WRITE = "write"
+ # ADMIN = "admin"
+ NA = "na"
+
+
+class PermissionScope(StrEnumMixin, Enum):
+ SITE = "site"
+ DOMAIN = "domain"
+ ENTITY = "entity"
+ USER = "user"
+ APP = "app"
diff --git a/brick_server/minimal/schemas/timeseries.py b/brick_server/minimal/schemas/timeseries.py
new file mode 100644
index 0000000..1c2fda3
--- /dev/null
+++ b/brick_server/minimal/schemas/timeseries.py
@@ -0,0 +1,23 @@
+from enum import Enum
+from typing import Any
+
+from brick_server.minimal.schemas.base import BaseModel, StrEnumMixin
+
+
+class ValueType(StrEnumMixin, Enum):
+ number = "number"
+ text = "text"
+ loc = "loc"
+
+
+class ColumnType(StrEnumMixin, Enum):
+ number = "number"
+ text = "text"
+ loc = "loc"
+ uuid = "uuid"
+ timestamp = "timestamp"
+
+
+class TimeseriesData(BaseModel):
+ data: list[list[Any]]
+ columns: list[ColumnType] = ["uuid", "timestamp", "number"]
diff --git a/brick_server/minimal/schemas/user.py b/brick_server/minimal/schemas/user.py
new file mode 100644
index 0000000..94e9d8c
--- /dev/null
+++ b/brick_server/minimal/schemas/user.py
@@ -0,0 +1,16 @@
+from beanie import PydanticObjectId
+from fastapi_users import schemas
+
+from brick_server.minimal.schemas.base import BaseModel
+
+
+class UserRead(schemas.BaseUser[PydanticObjectId], BaseModel):
+ name: str
+
+
+class UserCreate(schemas.BaseUserCreate, BaseModel):
+ name: str
+
+
+class UserUpdate(schemas.BaseUserUpdate, BaseModel):
+ name: str | None = None
diff --git a/brick_server/minimal/securities/__init__.py b/brick_server/minimal/securities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/brick_server/minimal/securities/auth.py b/brick_server/minimal/securities/auth.py
new file mode 100644
index 0000000..4bf0ada
--- /dev/null
+++ b/brick_server/minimal/securities/auth.py
@@ -0,0 +1,222 @@
+from typing import Any, Callable, Optional, Set
+
+import jwt
+from beanie import PydanticObjectId
+from fastapi import Depends, Request, Response, status
+from fastapi.responses import RedirectResponse
+from fastapi_users import BaseUserManager, FastAPIUsers, models
+from fastapi_users.authentication import (
+ AuthenticationBackend,
+ BearerTransport,
+ CookieTransport,
+ JWTStrategy,
+ Strategy,
+)
+from fastapi_users.authentication.authenticator import (
+ name_to_strategy_variable_name,
+ name_to_variable_name,
+)
+from fastapi_users.jwt import decode_jwt, generate_jwt
+from fastapi_users_db_beanie import BeanieUserDatabase, ObjectIDIDMixin
+from makefun import with_signature
+
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.models.user import User
+from brick_server.minimal.schemas.oauth_account import OAuthAccount
+from brick_server.minimal.schemas.permission import PermissionScope, PermissionType
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
+
+class RedirectCookieTransport(CookieTransport):
+ async def get_login_response(self, token: str) -> Response:
+ response = RedirectResponse(
+ settings.FRONTEND_URL, status_code=status.HTTP_303_SEE_OTHER
+ )
+ return self._set_login_cookie(response, token)
+
+
+cookie_transport = RedirectCookieTransport(
+ cookie_secure=not settings.DEBUG, cookie_max_age=settings.JWT_EXPIRE_SECONDS
+)
+bearer_transport = BearerTransport(tokenUrl="/brickapi/v1/auth/bearer/login")
+
+
+class MyJWTStrategy(JWTStrategy):
+ async def read_token(
+ self, token: Optional[str], user_manager: BaseUserManager[models.UP, models.ID]
+ ) -> Optional[dict]:
+ if token is None:
+ return None
+
+ try:
+ data = decode_jwt(
+ token, self.decode_key, self.token_audience, algorithms=[self.algorithm]
+ )
+ except jwt.PyJWTError:
+ return None
+
+ return data
+
+ async def write_token(self, user: models.UP) -> str:
+ data = {
+ "sub": str(user.email),
+ "aud": self.token_audience,
+ }
+ return generate_jwt(
+ data, self.encode_key, self.lifetime_seconds, algorithm=self.algorithm
+ )
+
+
+def get_jwt_strategy() -> MyJWTStrategy:
+ return MyJWTStrategy(
+ secret=settings.JWT_SECRET_KEY,
+ lifetime_seconds=settings.JWT_EXPIRE_SECONDS,
+ token_audience=[settings.JWT_TOKEN_PREFIX],
+ algorithm=settings.JWT_ALGORITHM,
+ )
+
+
+my_jwt_strategy = get_jwt_strategy()
+
+
+cookie_auth_backend = AuthenticationBackend(
+ name="cookie",
+ transport=cookie_transport,
+ get_strategy=get_jwt_strategy,
+)
+
+bearer_auth_backend = AuthenticationBackend(
+ name="bearer",
+ transport=bearer_transport,
+ get_strategy=get_jwt_strategy,
+)
+
+
+class UserDatabase(BeanieUserDatabase):
+ async def get_by_name(self, string: str) -> Optional[User]:
+ """Get a single user by user_id."""
+ return await self.user_model.find_one(
+ self.user_model.name == string,
+ collation=self.user_model.Settings.name_collation,
+ )
+
+ async def create(self, create_dict: dict[str, Any]) -> User:
+ """Create a user."""
+ if "name" not in create_dict:
+ create_dict["name"] = create_dict["email"]
+ user = self.user_model(**create_dict)
+ await user.create()
+ return user
+
+
+class UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]):
+ reset_password_token_secret = settings.JWT_SECRET_KEY
+ verification_token_secret = settings.JWT_SECRET_KEY
+
+ # async def get_by_email(self, user_email: str) -> User:
+ # self.user_db: UserDatabase
+ # user = await self.user_db.get_by_email(user_email)
+ # if user is None:
+ # user = await self.user_db.get_by_name(user_email)
+ # if user is None:
+ # raise exceptions.UserNotExists()
+ # return user
+
+ async def on_after_register(self, user: User, request: Optional[Request] = None):
+ print(f"User {user.email} has registered.")
+
+ async def on_after_forgot_password(
+ self, user: User, token: str, request: Optional[Request] = None
+ ):
+ print(f"User {user.email} has forgot their password. Reset token: {token}")
+
+ async def on_after_request_verify(
+ self, user: User, token: str, request: Optional[Request] = None
+ ):
+ print(
+ f"Verification requested for user {user.email}. Verification token: {token}"
+ )
+
+ async def validate_password(self, password: str, user: User) -> None:
+ pass
+ # if not re.match(PASSWORD_REGEX, password):
+ # raise InvalidPasswordException("Password does not meet requirements")
+
+
+async def get_user_db():
+ yield UserDatabase(User, OAuthAccount)
+
+
+async def get_user_manager(user_db=Depends(get_user_db)):
+ yield UserManager(user_db)
+
+
+fastapi_users = FastAPIUsers[User, PydanticObjectId](
+ get_user_manager,
+ [bearer_auth_backend, cookie_auth_backend],
+)
+
+# current_active_user = fastapi_users.current_user(active=True)
+# current_user_token = fastapi_users.authenticator.current_user_token(active=True)
+
+
+async def jwt_authenticate(
+ *args, user_manager: BaseUserManager[models.UP, models.ID], **kwargs
+) -> Optional[dict]:
+ data: Optional[dict] = None
+ for backend in fastapi_users.authenticator.backends:
+ token = kwargs[name_to_variable_name(backend.name)]
+ strategy: Strategy[models.UP, models.ID] = kwargs[
+ name_to_strategy_variable_name(backend.name)
+ ]
+ if token is not None:
+ data = await strategy.read_token(token, user_manager)
+ if data:
+ break
+ if not data:
+ raise BizError(ErrorCode.UnauthorizedError)
+ return data
+
+
+signature = fastapi_users.authenticator._get_dependency_signature()
+
+
+@with_signature(signature)
+async def get_jwt_payload(*args, **kwargs):
+ return await jwt_authenticate(
+ *args,
+ **kwargs,
+ )
+
+
+async def get_token_user(
+ token: Optional[dict] = Depends(get_jwt_payload),
+ user_manager: UserManager = Depends(get_user_manager),
+) -> User:
+ try:
+ user_email = token.get("sub")
+ user = await user_manager.get_by_email(user_email)
+ return user
+ except Exception:
+ raise BizError(ErrorCode.UnauthorizedError)
+
+
+def default_auth_logic(
+ user: User = Depends(get_token_user),
+) -> Callable[[Set[str], PermissionType, PermissionScope], bool]:
+ def _auth_logic(
+ entity_ids: Set[str],
+ permission_type: PermissionType,
+ permission_scope: PermissionScope,
+ ):
+ # we use a very simple permission model in brick server minimal
+ # there are only two api permission levels: admin and non-admin
+ if (
+ permission_scope == PermissionScope.SITE
+ or permission_scope == PermissionScope.DOMAIN
+ ):
+ if not user.is_superuser:
+ return False
+ return True
+
+ return _auth_logic
diff --git a/brick_server/minimal/securities/checker.py b/brick_server/minimal/securities/checker.py
new file mode 100644
index 0000000..070bb4c
--- /dev/null
+++ b/brick_server/minimal/securities/checker.py
@@ -0,0 +1,106 @@
+import abc
+import asyncio
+from typing import Callable, Dict, Set, Tuple, Union
+
+from fastapi import Body, Depends, Query
+
+from brick_server.minimal.schemas import PermissionScope, PermissionType, TimeseriesData
+from brick_server.minimal.utilities.dependencies import dependency_supplier
+from brick_server.minimal.utilities.descriptions import Descriptions
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
+
+class PermissionCheckerBase(abc.ABC):
+ def __init__(
+ self,
+ permission_type: PermissionType = PermissionType.NA,
+ permission_scope: PermissionScope = PermissionScope.ENTITY,
+ ):
+ self.permission_type = permission_type
+ self.permission_scope = permission_scope
+
+ @staticmethod
+ async def call_auth_logic(
+ auth_logic,
+ entity_ids: Set[str],
+ permission_type: PermissionType,
+ permission_scope: PermissionScope,
+ ):
+ if hasattr(auth_logic, "__call__"):
+ auth_logic_func = auth_logic.__call__
+ else:
+ auth_logic_func = auth_logic
+ if asyncio.iscoroutinefunction(auth_logic_func):
+ result = await auth_logic_func(
+ entity_ids, permission_type, permission_scope
+ )
+ else:
+ result = auth_logic_func(entity_ids, permission_type, permission_scope)
+ if not result:
+ raise BizError(ErrorCode.UnauthorizedError)
+ return auth_logic
+
+
+class PermissionChecker(PermissionCheckerBase):
+ async def __call__(
+ self,
+ # token: HTTPAuthorizationCredentials = jwt_security_scheme,
+ auth_logic: Callable[
+ [Set[str], PermissionType, PermissionScope], bool
+ ] = Depends(dependency_supplier.auth_logic),
+ ):
+ return await self.call_auth_logic(
+ auth_logic, set(), self.permission_type, self.permission_scope
+ )
+
+
+class PermissionCheckerWithEntityId(PermissionCheckerBase):
+ async def __call__(
+ self,
+ # token: HTTPAuthorizationCredentials = jwt_security_scheme,
+ auth_logic: Callable[
+ [Set[str], PermissionType, PermissionScope], bool
+ ] = Depends(dependency_supplier.auth_logic),
+ entity_id: str = Query(..., description=""),
+ ):
+ return await self.call_auth_logic(
+ auth_logic, {entity_id}, self.permission_type, self.permission_scope
+ )
+
+
+class PermissionCheckerWithData(PermissionCheckerBase):
+ @staticmethod
+ def get_entity_ids(data: TimeseriesData) -> Set[str]:
+ rows = data.data
+ columns = data.columns
+ uuid_idx = columns.index("uuid")
+ uuids = {row[uuid_idx] for row in rows}
+ return uuids
+
+ async def __call__(
+ self,
+ # token: HTTPAuthorizationCredentials = jwt_security_scheme,
+ auth_logic: Callable[
+ [Set[str], PermissionType, PermissionScope], bool
+ ] = Depends(dependency_supplier.auth_logic),
+ data: TimeseriesData = Body(..., description=Descriptions.timeseries_data),
+ ):
+ entity_ids = self.get_entity_ids(data)
+ return await self.call_auth_logic(
+ auth_logic, entity_ids, self.permission_type, self.permission_scope
+ )
+
+
+class PermissionCheckerActuation(PermissionCheckerBase):
+ async def __call__(
+ self,
+ # token: HTTPAuthorizationCredentials = jwt_security_scheme,
+ auth_logic: Callable[
+ [Set[str], PermissionType, PermissionScope], bool
+ ] = Depends(dependency_supplier.auth_logic),
+ actuation_request: Dict[str, Union[Tuple[str], Tuple[str, str]]] = Body(...),
+ ):
+ entity_ids = set(actuation_request.keys())
+ return await self.call_auth_logic(
+ auth_logic, entity_ids, self.permission_type, self.permission_scope
+ )
diff --git a/brick_server/minimal/securities/oauth.py b/brick_server/minimal/securities/oauth.py
new file mode 100644
index 0000000..8a6db3d
--- /dev/null
+++ b/brick_server/minimal/securities/oauth.py
@@ -0,0 +1,8 @@
+from httpx_oauth.clients.google import GoogleOAuth2
+
+from brick_server.minimal.config.manager import settings
+
+oauth_google_client = GoogleOAuth2(
+ client_id=settings.OAUTH_GOOGLE_CLIENT_ID,
+ client_secret=settings.OAUTH_GOOGLE_CLIENT_SECRET,
+)
diff --git a/brick_server/minimal/services/__init__.py b/brick_server/minimal/services/__init__.py
index e69de29..f99d0c4 100644
--- a/brick_server/minimal/services/__init__.py
+++ b/brick_server/minimal/services/__init__.py
@@ -0,0 +1,23 @@
+from fastapi import APIRouter
+from starlette.responses import RedirectResponse
+
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.services.actuation import router as actuation_router
+from brick_server.minimal.services.auth import router as auth_router
+from brick_server.minimal.services.data import router as data_router
+from brick_server.minimal.services.domain import router as domain_router
+from brick_server.minimal.services.user import router as user_router
+
+router = APIRouter()
+
+
+@router.get("/", include_in_schema=False)
+def redirect_docs():
+ return RedirectResponse(url=settings.DOCS_URL)
+
+
+router.include_router(router=auth_router)
+router.include_router(router=user_router)
+router.include_router(router=domain_router)
+router.include_router(router=actuation_router)
+router.include_router(router=data_router)
diff --git a/brick_server/minimal/services/entities.py b/brick_server/minimal/services/_entities.py
similarity index 94%
rename from brick_server/minimal/services/entities.py
rename to brick_server/minimal/services/_entities.py
index f345a4e..4ee24ea 100644
--- a/brick_server/minimal/services/entities.py
+++ b/brick_server/minimal/services/_entities.py
@@ -1,29 +1,27 @@
from typing import Any, Callable, List
from fastapi import Body, Depends, HTTPException, Query
-from fastapi.security import HTTPAuthorizationCredentials
-from fastapi_rest_framework.config import settings
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
+from fastapi_restful.cbv import cbv
+from fastapi_restful.inferring_router import InferringRouter
from loguru import logger
from pydantic import BaseModel
from starlette.requests import Request
-from brick_server.minimal.auth.authorization import (
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.interfaces import GraphDB
+from brick_server.minimal.models import Domain
+from brick_server.minimal.schemas import Entity, EntityIds
+from brick_server.minimal.securities.checker import (
PermissionChecker,
PermissionCheckerWithEntityId,
PermissionType,
- jwt_security_scheme,
)
-from brick_server.minimal.dependencies import (
+from brick_server.minimal.utilities.dependencies import (
dependency_supplier,
get_graphdb,
query_domain,
)
-from brick_server.minimal.descriptions import Descriptions
-from brick_server.minimal.interfaces import GraphDB
-from brick_server.minimal.models import Domain
-from brick_server.minimal.schemas import Entity, EntityIds
+from brick_server.minimal.utilities.descriptions import Descriptions
entity_router = InferringRouter(tags=["Entities"])
@@ -107,7 +105,7 @@ async def get_name(graphdb, domain: Domain, entity_id: str):
# background_tasks: BackgroundTasks,
# file: UploadFile = File(...),
# named_graph: Optional[str] = Query(None, description=Descriptions.graph),
-# checker: Any = Depends(PermissionChecker(PermissionType.write)),
+# checker: Any = Depends(PermissionChecker(PermissionType.WRITE)),
# ):
# await self.graphdb.clear_import_file(file.filename)
# background_tasks.add_task(
@@ -139,7 +137,7 @@ async def get_name(graphdb, domain: Domain, entity_id: str):
# ),
# content_type: str = Header("text/turtle"),
# token: HTTPAuthorizationCredentials = jwt_security_scheme,
-# checker: Any = Depends(PermissionChecker(PermissionType.write)),
+# checker: Any = Depends(PermissionChecker(PermissionType.WRITE)),
# ) -> IsSuccess:
# jwt_payload = parse_jwt_token(token.credentials)
# user_id = jwt_payload["user_id"]
@@ -177,7 +175,6 @@ async def get_name(graphdb, domain: Domain, entity_id: str):
@cbv(entity_router)
class EntitiesByIdResource:
-
auth_logic: Callable = Depends(dependency_supplier.auth_logic)
graphdb: GraphDB = Depends(get_graphdb)
@@ -194,7 +191,7 @@ async def get_entity_by_id(
request: Request,
entity_id: str = Query(..., description=Descriptions.entity_id),
domain: Domain = Depends(query_domain),
- checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.read)),
+ checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.READ)),
) -> Entity:
print(entity_id)
entity_types = await get_entity_types(self.graphdb, domain, entity_id)
@@ -223,7 +220,7 @@ async def get_entity_by_id(
# self,
# request: Request,
# entity_id: str = Path(..., description=Descriptions.entity_id),
- # checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.write)),
+ # checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.WRITE)),
# ) -> IsSuccess:
# futures = []
# qstr = """
@@ -266,7 +263,7 @@ async def get_entity_by_id(
# relationships: Relationships = Body(
# ..., description=Descriptions.relationships
# ),
- # checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.write)),
+ # checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.WRITE)),
# ):
# for [prop, obj] in relationships:
# await self.brick_db.add_triple(URIRef(entity_id), prop, obj)
@@ -307,7 +304,6 @@ class ListEntityParams(BaseModel):
# TODO: In the auth model, this resource's target is a `graph`
@cbv(entity_router)
class EntitiesResource:
-
auth_logic: Callable = Depends(dependency_supplier.auth_logic)
graphdb: GraphDB = Depends(get_graphdb)
@@ -324,11 +320,10 @@ async def post(
ListEntityParams(), description=Descriptions.relation_query
),
domain: Domain = Depends(query_domain),
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- checker: Any = Depends(PermissionChecker(PermissionType.write)),
+ checker: Any = Depends(PermissionChecker(PermissionType.WRITE)),
) -> EntityIds:
# FIXME: rewrite
- topclass = get_brick_topclass(settings.brick_version)
+ topclass = get_brick_topclass(settings.BRICK_VERSION)
logger.debug("topclass: {}", topclass)
qstr = f"""
select distinct ?entity where {{
@@ -357,7 +352,7 @@ async def post(
# description="A dictionary to describe entities to create. Keys are Brick Classes and values are the number of instances to create for the Class",
# ),
# graph: str = Query(settings.brick_base_graph, description=Descriptions.graph),
- # checker: Any = Depends(PermissionChecker(PermissionType.read)),
+ # checker: Any = Depends(PermissionChecker(PermissionType.READ)),
# ) -> EntitiesCreateResponse:
# resp = defaultdict(list)
# for brick_type, entities_num in create_entities.items():
diff --git a/brick_server/minimal/services/actuation.py b/brick_server/minimal/services/actuation.py
index 10c4a01..d30a1eb 100644
--- a/brick_server/minimal/services/actuation.py
+++ b/brick_server/minimal/services/actuation.py
@@ -1,63 +1,183 @@
-from typing import Callable, Dict, Tuple, Union
+import asyncio
+from typing import Any, Dict, Tuple, Union
-from fastapi import Body, Depends, status
-from fastapi.exceptions import HTTPException
-from fastapi.security import HTTPAuthorizationCredentials
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
+import arrow
+from fastapi import APIRouter, Body, Depends
+from fastapi_restful.cbv import cbv
from starlette.requests import Request
-from brick_server.minimal.auth.authorization import jwt_security_scheme
-from brick_server.minimal.dependencies import (
- dependency_supplier,
+from brick_server.minimal import models, schemas
+from brick_server.minimal.interfaces import ActuationInterface, BaseTimeseries, GraphDB
+from brick_server.minimal.securities.auth import get_jwt_payload
+from brick_server.minimal.securities.checker import PermissionCheckerActuation
+from brick_server.minimal.utilities.dependencies import (
get_actuation_iface,
+ get_graphdb,
+ get_path_domain,
get_ts_db,
- query_domain,
)
-from brick_server.minimal.interfaces import BaseTimeseries, RealActuation
-from brick_server.minimal.schemas import Domain, IsSuccess
-actuation_router = InferringRouter(tags=["Actuation"])
+router = APIRouter(prefix="/actuation", tags=["actuation"])
-@cbv(actuation_router)
+@cbv(router)
class ActuationEntity:
- actuation_iface: RealActuation = Depends(get_actuation_iface)
+ actuation_iface: ActuationInterface = Depends(get_actuation_iface)
ts_db: BaseTimeseries = Depends(get_ts_db)
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
+ graphdb: GraphDB = Depends(get_graphdb)
- @actuation_router.post(
- "/",
+ # TODO: use a better method to guard the playground api
+ async def guard_before_actuation(
+ self, domain, entity_id, value
+ ) -> Tuple[bool, str, float, float]:
+ pass
+
+ async def read_entity(self, domain, entity_id):
+ success, detail = await self.actuation_iface.read(domain, entity_id)
+ return success, detail
+
+ async def actuate_entity(self, domain, jwt_payload, entity_id, actuation_payload):
+ policy_time = 0
+ guard_time = 0
+ driver_time = 0
+ actuation_time = 0
+ try:
+ (
+ success,
+ detail,
+ policy_time,
+ guard_time,
+ ) = await self.guard_before_actuation(
+ domain, entity_id, actuation_payload[0]
+ )
+ if not success:
+ return (
+ success,
+ detail,
+ (policy_time, guard_time, driver_time, actuation_time),
+ )
+ if len(actuation_payload) == 2:
+ print(actuation_payload[1])
+ (
+ success,
+ detail,
+ driver_time,
+ actuation_time,
+ ) = await self.actuation_iface.actuate(
+ domain, entity_id, actuation_payload[0]
+ )
+ await self.ts_db.add_history_data(
+ domain.name,
+ entity_id,
+ jwt_payload["sub"],
+ jwt_payload["app_name"],
+ jwt_payload.get("domain_user_app", ""),
+ arrow.now(),
+ actuation_payload[0],
+ )
+ return (
+ success,
+ detail,
+ (policy_time, guard_time, driver_time, actuation_time),
+ )
+ except Exception as e:
+ return False, f"{e}", (policy_time, guard_time, driver_time, actuation_time)
+
+ @router.post(
+ "/domains/{domain}/read",
+ description="Read an entity to a value. ",
+ dependencies=[
+ Depends(
+ PermissionCheckerActuation(permission_type=schemas.PermissionType.READ)
+ )
+ ],
+ )
+ async def read(
+ self,
+ domain: models.Domain = Depends(get_path_domain),
+ actuation_request: Dict[str, Union[Tuple[str], Tuple[str, str]]] = Body(...),
+ ) -> schemas.StandardResponse[schemas.ActuationResults]:
+ tasks = [
+ self.read_entity(domain, entity_id)
+ for entity_id in actuation_request.keys()
+ ]
+ task_results = await asyncio.gather(*tasks)
+ results = []
+ for success, detail in task_results:
+ results.append(schemas.ActuationResult(success=success, detail=detail))
+ return schemas.ActuationResults(results=results).to_response()
+
+ @router.post(
+ "/domains/{domain}",
description="Actuate an entity to a value. Body format {{entity_id: [actuation_value, optional playceholder}, ...}",
- response_model=IsSuccess,
- status_code=200,
+ dependencies=[
+ Depends(
+ PermissionCheckerActuation(permission_type=schemas.PermissionType.WRITE)
+ )
+ ],
)
async def post(
self,
request: Request,
- domain: Domain = Depends(query_domain),
- # entity_id: str = Query(..., description=Descriptions.entity_id),
- actuation_request: Dict[str, Union[Tuple[str], Tuple[str, str]]] = Body(
- ...,
- ),
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- ) -> IsSuccess:
+ domain: models.Domain = Depends(get_path_domain),
+ actuation_request: Dict[str, Union[Tuple[str], Tuple[str, str]]] = Body(...),
+ jwt_payload: Dict[str, Any] = Depends(get_jwt_payload),
+ ) -> schemas.StandardResponse[schemas.ActuationResults]:
# if scheduled_time:
# # TODO: Implement this
# raise exceptions.NotImplemented('Currently only immediate actuation is supported.')
# actuation_key = actuation_request.key
# actuation_value = actuation_request.key
- for entity_id, actuation in actuation_request.items():
- try:
- if len(actuation) == 2:
- print(actuation[1])
- result, detail = self.actuation_iface.actuate(entity_id, actuation[0])
- return IsSuccess(is_success=result, reason=detail)
- except Exception as e:
- return IsSuccess(is_success=False, reason=f"{e}")
-
- raise HTTPException(status.HTTP_400_BAD_REQUEST, "This should not be reached.")
-
- def relinquish(self, entity_id):
- pass
+
+ tasks = [
+ self.actuate_entity(domain, jwt_payload, entity_id, actuation_payload)
+ for entity_id, actuation_payload in actuation_request.items()
+ ]
+ task_results = await asyncio.gather(*tasks)
+ results = []
+ response_time = {"policy": 0, "guard": 0, "driver": 0, "actuation": 0}
+
+ for (
+ success,
+ detail,
+ (policy_time, guard_time, driver_time, actuation_time),
+ ) in task_results:
+ results.append(schemas.ActuationResult(success=success, detail=detail))
+ response_time["policy"] += 1000 * policy_time
+ response_time["guard"] += 1000 * guard_time
+ response_time["driver"] += 1000 * driver_time
+ response_time["actuation"] += 1000 * actuation_time
+
+ # timer: _TimingStats = getattr(request.state, TIMER_ATTRIBUTE)
+ # timer.take_split()
+
+ return schemas.ActuationResults(
+ results=results, response_time=response_time
+ ).to_response()
+
+ # for entity_id, actuation in actuation_request.items():
+ # if not await self.guard_before_actuation(domain, entity_id):
+ # continue
+ # await self.ts_db.add_history_data(
+ # domain.name,
+ # entity_id,
+ # jwt_payload["user_id"],
+ # jwt_payload["app_name"],
+ # jwt_payload.get("domain_user_app", ""),
+ # arrow.now(),
+ # actuation[0],
+ # )
+ #
+ # # return schemas.IsSuccess()
+ #
+ # for entity_id, actuation in actuation_request.items():
+ # try:
+ # if len(actuation) == 2:
+ # print(actuation[1])
+ # result, detail = self.actuation_iface.actuate(entity_id, actuation[0])
+ # return schemas.IsSuccess(is_success=result, reason=detail)
+ # except Exception as e:
+ # return schemas.IsSuccess(is_success=False, reason=f"{e}")
+ #
+ # raise HTTPException(status.HTTP_400_BAD_REQUEST, "This should not be reached.")
diff --git a/brick_server/minimal/services/auth.py b/brick_server/minimal/services/auth.py
new file mode 100644
index 0000000..d9f7b63
--- /dev/null
+++ b/brick_server/minimal/services/auth.py
@@ -0,0 +1,88 @@
+from fastapi import APIRouter, Depends, Request
+from fastapi_users import (
+ BaseUserManager,
+ exceptions as fastapi_users_exceptions,
+ models as fastapi_users_models,
+)
+
+from brick_server.minimal import schemas
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.securities.auth import (
+ bearer_auth_backend,
+ cookie_auth_backend,
+ fastapi_users,
+ get_user_manager,
+)
+from brick_server.minimal.securities.oauth import oauth_google_client
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
+router = APIRouter(prefix="/auth", tags=["auth"])
+
+user_schema = schemas.UserRead
+user_create_schema = schemas.UserCreate
+
+# generate /login and /logout routes for cookie authentication backend
+router.include_router(
+ fastapi_users.get_auth_router(cookie_auth_backend),
+ prefix="/cookie",
+)
+
+# generate /login and /logout routes for jwt authentication backend
+router.include_router(
+ fastapi_users.get_auth_router(bearer_auth_backend),
+ prefix="/bearer",
+)
+
+
+# generate a /register route to allow a user to create a new account
+# router.include_router(
+# fastapi_users.get_register_router(schemas.UserRead, schemas.UserCreate),
+# )
+@router.post(
+ "/register",
+ name="register:register",
+)
+async def register(
+ request: Request,
+ user_create: user_create_schema,
+ user_manager: BaseUserManager[
+ fastapi_users_models.UP, fastapi_users_models.ID
+ ] = Depends(get_user_manager),
+) -> schemas.StandardResponse[user_schema]:
+ try:
+ created_user = await user_manager.create(
+ user_create, safe=True, request=request
+ )
+ except fastapi_users_exceptions.UserAlreadyExists:
+ raise BizError(ErrorCode.UserAlreadyExistsError)
+ except fastapi_users_exceptions.InvalidPasswordException as e:
+ raise BizError(ErrorCode.UserInvalidPasswordError, e.reason)
+
+ return user_schema.model_validate(created_user).to_response()
+
+
+# generate a /verify route to manage user email verification
+router.include_router(
+ fastapi_users.get_verify_router(schemas.UserRead),
+)
+
+# generate /forgot-password (the user asks for a token to reset its password) and
+# /reset-password (the user changes its password given the token) routes
+router.include_router(
+ fastapi_users.get_reset_password_router(),
+)
+
+for auth_type, auth_backend in (
+ ("cookie", cookie_auth_backend),
+ ("bearer", bearer_auth_backend),
+):
+ router.include_router(
+ fastapi_users.get_oauth_router(
+ oauth_google_client,
+ auth_backend,
+ settings.JWT_SECRET_KEY,
+ associate_by_email=True,
+ is_verified_by_default=True,
+ ),
+ prefix=f"/{auth_type}/google",
+ )
diff --git a/brick_server/minimal/services/data.py b/brick_server/minimal/services/data.py
index f2587a8..93723c0 100644
--- a/brick_server/minimal/services/data.py
+++ b/brick_server/minimal/services/data.py
@@ -1,100 +1,113 @@
import asyncio
-from typing import Any, Callable
+from typing import Callable
-from fastapi import Body, Depends, HTTPException, Query, status
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
+from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
+from fastapi_restful.cbv import cbv
from loguru import logger
-from brick_server.minimal.auth.authorization import (
- PermissionCheckerWithData,
- PermissionCheckerWithEntityId,
- PermissionType,
-)
-from brick_server.minimal.dependencies import (
+from brick_server.minimal import models, schemas
+from brick_server.minimal.interfaces import TimeseriesInterface
+from brick_server.minimal.securities.checker import PermissionCheckerWithEntityId
+from brick_server.minimal.utilities.dependencies import (
dependency_supplier,
- get_ts_db,
- query_domain,
-)
-from brick_server.minimal.descriptions import Descriptions
-from brick_server.minimal.interfaces import AsyncpgTimeseries
-from brick_server.minimal.schemas import (
- Domain,
- IsSuccess,
- TimeseriesData,
- ValueType,
- ValueTypes,
+ get_pagination_query,
+ get_path_domain,
+ get_timeseries_iface,
)
+from brick_server.minimal.utilities.descriptions import Descriptions
-data_router = InferringRouter(tags=["Data"])
+router = APIRouter(prefix="/data", tags=["data"])
-@cbv(data_router)
+@cbv(router)
class Timeseries:
- ts_db: AsyncpgTimeseries = Depends(get_ts_db)
+ ts_db: TimeseriesInterface = Depends(get_timeseries_iface)
auth_logic: Callable = Depends(dependency_supplier.auth_logic)
- @data_router.get(
- "/timeseries",
+ @router.get(
+ "/timeseries/domains/{domain}",
status_code=200,
- # description='Get data of an entity with in a time range.',
- response_model=TimeseriesData,
+ description="Get data of an entity with in a time range.",
+ dependencies=[
+ Depends(
+ PermissionCheckerWithEntityId(
+ permission_type=schemas.PermissionType.READ
+ )
+ )
+ ],
)
async def get(
self,
- domain: Domain = Depends(query_domain),
+ domain: models.Domain = Depends(get_path_domain),
entity_id: str = Query(
...,
description=Descriptions.entity_id,
),
start_time: float = Query(default=None, description=Descriptions.start_time),
end_time: float = Query(default=None, description=Descriptions.end_time),
- value_types: ValueTypes = Query(
- default=[ValueType.number],
+ value_types: list[schemas.ValueType] = Query(
+ default=[schemas.ValueType.number],
description=Descriptions.value_type,
),
- checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.read)),
- ) -> TimeseriesData:
+ pagination: schemas.PaginationQuery = Depends(get_pagination_query),
+ # checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.READ)),
+ ) -> schemas.StandardResponse[schemas.TimeseriesData]:
value_types = [row.value for row in value_types]
data = await self.ts_db.query(
- domain.name, [entity_id], start_time, end_time, value_types
+ domain,
+ [entity_id],
+ start_time,
+ end_time,
+ # value_types,
+ pagination.limit,
+ pagination.offset,
)
columns = ["uuid", "timestamp"] + value_types
- return TimeseriesData(data=data, columns=columns)
+ return schemas.TimeseriesData(data=data, columns=columns).to_response()
- @data_router.delete(
- "/timeseries",
+ @router.delete(
+ "/timeseries/domains/{domain}",
status_code=200,
description="Delete data of an entity with in a time range or all the data if a time range is not given.",
- response_model=IsSuccess,
+ dependencies=[
+ Depends(
+ PermissionCheckerWithEntityId(
+ permission_scope=schemas.PermissionScope.DOMAIN
+ )
+ )
+ ],
)
async def delete(
self,
- domain: Domain = Depends(query_domain),
+ domain: models.Domain = Depends(get_path_domain),
entity_id: str = Query(..., description=Descriptions.entity_id),
start_time: float = Query(default=None, description=Descriptions.start_time),
end_time: float = Query(None, description=Descriptions.end_time),
- checker: Any = Depends(PermissionCheckerWithEntityId(PermissionType.write)),
- ) -> IsSuccess:
+ ) -> schemas.StandardResponse[schemas.Empty]:
# self.auth_logic(entity_id, "write")
await self.ts_db.delete(domain.name, [entity_id], start_time, end_time)
- return IsSuccess()
+ return schemas.StandardResponse()
- @data_router.post(
- "/timeseries",
+ @router.post(
+ "/timeseries/domains/{domain}",
status_code=200,
description="Post data. If fields are not given, default values are assumed.",
- response_model=IsSuccess,
+ dependencies=[
+ Depends(
+ PermissionCheckerWithEntityId(
+ permission_scope=schemas.PermissionScope.DOMAIN
+ )
+ )
+ ],
)
async def post(
self,
- domain: Domain = Depends(query_domain),
- data: TimeseriesData = Body(
+ domain: models.Domain = Depends(get_path_domain),
+ data: schemas.TimeseriesData = Body(
...,
description=Descriptions.timeseries_data,
),
- checker: Any = Depends(PermissionCheckerWithData(PermissionType.write)),
- ) -> IsSuccess:
+ ) -> schemas.StandardResponse[schemas.Empty]:
raw_data = data.data
if not raw_data:
raise HTTPException(
@@ -130,7 +143,7 @@ async def post(
logger.info(f"data {datum} with error {e}")
futures = self.add_data(domain.name, data, data_type=value_col)
await asyncio.gather(futures)
- return IsSuccess()
+ return schemas.StandardResponse()
async def add_data(self, domain_name, data, data_type):
try:
diff --git a/brick_server/minimal/services/domain.py b/brick_server/minimal/services/domain.py
index e3f6947..ab4b151 100644
--- a/brick_server/minimal/services/domain.py
+++ b/brick_server/minimal/services/domain.py
@@ -1,103 +1,151 @@
-from typing import Any, Callable
+import asyncio
-from fastapi import BackgroundTasks, Depends, File, Path, UploadFile
-from fastapi_rest_framework.config import settings
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
+from fastapi import APIRouter, BackgroundTasks, Depends, File, Path, UploadFile
+from fastapi_restful.cbv import cbv
from loguru import logger
from brick_server.minimal import models, schemas
-from brick_server.minimal.auth.authorization import PermissionChecker, PermissionType
-from brick_server.minimal.dependencies import (
- dependency_supplier,
+from brick_server.minimal.config.manager import settings
+from brick_server.minimal.interfaces import AsyncpgTimeseries, GraphDB
+from brick_server.minimal.interfaces.cache import clear_cache
+from brick_server.minimal.securities.checker import PermissionChecker
+from brick_server.minimal.utilities.dependencies import (
get_graphdb,
+ get_path_domain,
get_ts_db,
- path_domain,
)
-from brick_server.minimal.exceptions import AlreadyExistsError
-from brick_server.minimal.interfaces import AsyncpgTimeseries, GraphDB
-from brick_server.minimal.schemas import IsSuccess
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
+# from brick_server.minimal.auth.checker import PermissionChecker, PermissionType
-domain_router = InferringRouter(tags=["Domains"])
+router = APIRouter(prefix="/domains", tags=["domains"])
-@cbv(domain_router)
+
+@cbv(router)
class DomainRoute:
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
graphdb: GraphDB = Depends(get_graphdb)
ts_db: AsyncpgTimeseries = Depends(get_ts_db)
- async def create_domain_background(self, domain: models.Domain):
- await self.graphdb.init_repository(domain.name)
- await self.ts_db.init_table(domain.name)
- graphs = await self.graphdb.list_graphs(domain.name)
- if settings.default_brick_url in graphs:
- logger.info("GraphDB Brick Schema found.")
+ async def initialize_rdf_schema(self, graphs, domain, url):
+ if url in graphs:
+ logger.info("GraphDB schema {} found in domain {}.", url, domain.name)
else:
- logger.info("GraphDB Brick Schema not found.")
- await self.graphdb.import_schema_from_url(
- domain.name, settings.default_brick_url
+ logger.info(
+ "GraphDB schema {} not found in domain {}, initializing...",
+ url,
+ domain.name,
)
+ await self.graphdb.import_schema_from_url(domain.name, url)
+
+ async def initialize_domain_background(self, domain: models.Domain):
+ tasks = [
+ self.graphdb.init_repository(domain.name),
+ self.ts_db.init_table(domain.name),
+ self.ts_db.init_history_table(domain.name),
+ ]
+ await asyncio.gather(*tasks)
+ graphs = await self.graphdb.list_graphs(domain.name)
+ await self.initialize_rdf_schema(graphs, domain, settings.DEFAULT_BRICK_URL)
+ await self.initialize_rdf_schema(
+ graphs, domain, settings.DEFAULT_REF_SCHEMA_URL
+ )
+ domain.initialized = True
+ await domain.save()
+
+ @router.get(
+ "/",
+ dependencies=[Depends(PermissionChecker())],
+ )
+ async def list_domains(
+ self,
+ ) -> schemas.StandardListResponse[schemas.DomainRead]:
+ domains = await models.Domain.find_all().to_list()
+ return schemas.StandardListResponse(
+ [schemas.DomainRead.model_validate(domain.dict()) for domain in domains]
+ )
- @domain_router.post("/{domain}")
+ @router.post(
+ "/{domain}",
+ dependencies=[
+ Depends(PermissionChecker(permission_scope=schemas.PermissionScope.SITE)),
+ ],
+ )
async def create_domain(
self,
background_tasks: BackgroundTasks,
domain: str = Path(...),
- checker: Any = Depends(PermissionChecker(PermissionType.write)),
- ) -> schemas.Domain:
+ ) -> schemas.StandardResponse[schemas.DomainRead]:
created_domain = models.Domain(name=domain)
try:
- created_domain.save()
+ await created_domain.save()
except Exception:
- raise AlreadyExistsError("domain", "name")
- background_tasks.add_task(self.create_domain_background, created_domain)
- return schemas.Domain.from_orm(created_domain)
+ raise BizError(ErrorCode.DomainAlreadyExistsError)
+ background_tasks.add_task(self.initialize_domain_background, created_domain)
+ return schemas.DomainRead.model_validate(created_domain.dict()).to_response()
- @domain_router.delete("/{domain}")
+ @router.delete(
+ "/{domain}",
+ dependencies=[
+ Depends(PermissionChecker(permission_scope=schemas.PermissionScope.SITE)),
+ ],
+ )
async def delete_domain(
self,
- domain: models.Domain = Depends(path_domain),
- checker: Any = Depends(PermissionChecker(PermissionType.write)),
- ):
+ domain: models.Domain = Depends(get_path_domain),
+ ) -> schemas.StandardResponse[schemas.Empty]:
# TODO: delete repository, add lock
- domain.delete()
- return IsSuccess()
+ await domain.delete()
+ return schemas.StandardResponse()
- @domain_router.get("/{domain}")
+ @router.get(
+ "/{domain}",
+ dependencies=[Depends(PermissionChecker())],
+ )
async def get_domain(
self,
- background_tasks: BackgroundTasks,
- domain: models.Domain = Depends(path_domain),
- checker: Any = Depends(PermissionChecker(PermissionType.read)),
- ) -> schemas.Domain:
- # for debug purpose
- background_tasks.add_task(self.create_domain_background, domain)
- return schemas.Domain.from_orm(domain)
+ domain: models.Domain = Depends(get_path_domain),
+ ) -> schemas.StandardResponse[schemas.DomainRead]:
+ return schemas.DomainRead.model_validate(domain.dict()).to_response()
+ @router.get(
+ "/{domain}/init",
+ dependencies=[
+ Depends(PermissionChecker(permission_scope=schemas.PermissionScope.DOMAIN))
+ ],
+ )
+ async def init_domain(
+ self,
+ domain: models.Domain = Depends(get_path_domain),
+ ) -> schemas.StandardResponse[schemas.DomainRead]:
+ # for debug purpose
+ await self.initialize_domain_background(domain)
+ return schemas.DomainRead.model_validate(domain.dict()).to_response()
-@cbv(domain_router)
-class DomainUploadRoute:
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
- graphdb: GraphDB = Depends(get_graphdb)
-
- @domain_router.post(
+ @router.post(
"/{domain}/upload",
- status_code=200,
- response_model=IsSuccess,
description="Upload a Turtle file. An example file: https://gitlab.com/jbkoh/brick-server-dev/blob/dev/examples/data/bldg.ttl",
summary="Uplaod a Turtle file",
+ dependencies=[
+ Depends(PermissionChecker(permission_scope=schemas.PermissionScope.DOMAIN)),
+ ],
)
- async def upload(
+ async def upload_turtle_file(
self,
background_tasks: BackgroundTasks,
- domain: models.Domain = Depends(path_domain),
+ domain: models.Domain = Depends(get_path_domain),
file: UploadFile = File(...),
- checker: Any = Depends(PermissionChecker(PermissionType.write)),
- ):
+ ) -> schemas.StandardResponse[schemas.DomainRead]:
await self.graphdb.clear_import_file(domain.name, file.filename)
- background_tasks.add_task(
- self.graphdb.import_schema_from_file, domain.name, file, delete=False
+ await self.graphdb.import_schema_from_file(
+ domain.name, file, named_graph=None, delete=False
)
- # await self.graphdb.import_schema_from_file(file, named_graph, delete=True)
- return IsSuccess()
+ await clear_cache(domain.name)
+ # background_tasks.add_task(
+ # self.graphdb.import_schema_from_file,
+ # domain.name,
+ # file,
+ # named_graph=None,
+ # delete=False,
+ # )
+ return schemas.DomainRead.model_validate(domain.dict()).to_response()
diff --git a/brick_server/minimal/services/grafana.py b/brick_server/minimal/services/grafana.py
deleted file mode 100644
index 396c5cc..0000000
--- a/brick_server/minimal/services/grafana.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import json
-from typing import Callable
-from uuid import uuid4 as gen_uuid
-
-from fastapi import Depends, HTTPException
-from fastapi.security import HTTPAuthorizationCredentials
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
-from starlette.requests import Request
-
-from brick_server.minimal.auth.authorization import (
- authenticated,
- jwt_security_scheme,
- parse_jwt_token,
-)
-from brick_server.minimal.dependencies import dependency_supplier, get_grafana
-from brick_server.minimal.models import GrafanaDashboard, User, get_doc
-from brick_server.minimal.schemas import GrafanaDashboardResponse
-
-from ..exceptions import AlreadyExistsError
-
-grafana_router = InferringRouter(tags=["Data"])
-
-
-@cbv(grafana_router)
-class GrafanaDashboardDetailsResource:
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
- grafana: Callable = Depends(get_grafana)
-
- @grafana_router.get(
- "/details",
- status_code=200,
- description="Get dashbaord metadata for the `uid`.",
- )
- @authenticated
- async def get(
- self,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- ):
- payload = parse_jwt_token(token.credentials)
- user_id = payload["user_id"]
- user = get_doc(User, user_id=user_id)
- gd = get_doc(GrafanaDashboard, user=user)
- uid = gd.uid
-
- headers = {"Accept": "application/json", "Content-Type": "application/json"}
- resp = await self.grafana.get(f"/dashboards/uid/{uid}", headers=headers)
- return resp.json()
-
-
-@cbv(grafana_router)
-class GrafanaDashboardResource:
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
- grafana: Callable = Depends(get_grafana)
-
- @grafana_router.get(
- "/",
- status_code=200,
- # description='Get data of an entity with in a time range.',
- response_model=GrafanaDashboardResponse,
- )
- @authenticated
- async def get(
- self,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- ) -> GrafanaDashboardResponse:
- payload = parse_jwt_token(token.credentials)
- user_id = payload["user_id"]
- user = get_doc(User, user_id=user_id)
- gd = get_doc(GrafanaDashboard, user=user)
- return GrafanaDashboardResponse(
- url=gd.url, grafana_id=gd.grafana_id, uid=gd.uid
- )
-
- async def create_grafana_dashboard(self, user):
- body = {
- "dashboard": {
- "uid": str(gen_uuid()),
- "title": user.user_id,
- }
- }
- resp = await self.grafana.post("/dashboards/db", json=body)
- if resp.status_code == 200:
- resp = resp.json()
- gd = GrafanaDashboard(
- user=user,
- uid=str(resp["uid"]),
- grafana_id=str(resp["id"]),
- url=resp["url"],
- )
- gd.save()
-
- return GrafanaDashboardResponse(
- url=gd.url,
- uid=gd.uid,
- grafana_id=gd.grafana_id,
- )
- else:
- if (
- resp.json()["message"]
- == "A dashboard with the same name in the folder already exists"
- ):
- raise AlreadyExistsError("Grafana Dashboard", user.user_id)
- else:
- raise HTTPException(detail="unknown error", status_code=500)
-
- async def update_grafana_dashboard(self, user, grafana_request):
- gd = get_doc(GrafanaDashboard, user=user)
- grafana_request["dashboard"]["uid"] = gd.uid
- grafana_request["dashboard"]["id"] = gd.grafana_id
- grafana_request["dashboard"]["title"] = user.user_id
- grafana_request["overwrite"] = True
-
- resp = await self.grafana.post("/dashboards/db", json=grafana_request)
- if resp.status_code == 200 and resp.json()["status"] == "success":
- return GrafanaDashboardResponse(
- url=gd.url,
- uid=gd.uid,
- grafana_id=gd.grafana_id,
- )
- else:
- msg = resp.json()["message"]
- if msg == "A dashboard with the same name in the folder already exists":
- raise AlreadyExistsError("Grafana Dashboard", user.user_id)
- else:
- raise HTTPException(detail=msg, status_code=500)
-
- @grafana_router.post(
- "/",
- status_code=200,
- description="Create or update the Grafana Dashboard. If JSON body is not given, it creates a Dashboard and assign it to the user. If JSON body is given, the body should be same as Grafana's dashboard model as defined at `https://grafana.com/docs/grafana/latest/http_api/dashboard/` except that uid, id, and title should be empty.",
- response_model=GrafanaDashboardResponse,
- )
- @authenticated
- async def post(
- self,
- request: Request,
- token: HTTPAuthorizationCredentials = jwt_security_scheme,
- # grafana_model: GrafanaUpdateRequest = Body(None, description='If given, update the existing Grafana dashboard assigned to the user. Otherwise, create a new Grafana Dashboard for the user.', required=False),
- ) -> GrafanaDashboardResponse:
- payload = parse_jwt_token(token.credentials)
- user_id = payload["user_id"]
- user = get_doc(User, user_id=user_id)
-
- body = await request.body()
- if body:
- grafana_request = json.loads(body)
- print("===========================")
- print(grafana_request)
- print("===========================")
- resp = await self.update_grafana_dashboard(user, grafana_request)
- else:
- resp = await self.create_grafana_dashboard(user)
- return resp
diff --git a/brick_server/minimal/services/queries.py b/brick_server/minimal/services/queries.py
deleted file mode 100644
index a74451a..0000000
--- a/brick_server/minimal/services/queries.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import calendar
-from datetime import datetime
-from typing import Any, Callable
-
-from fastapi import Body, Depends
-from fastapi_utils.cbv import cbv
-from fastapi_utils.inferring_router import InferringRouter
-from starlette.requests import Request
-
-from brick_server.minimal.auth.authorization import PermissionChecker, PermissionType
-from brick_server.minimal.dependencies import (
- dependency_supplier,
- get_graphdb,
- get_ts_db,
- query_domain,
-)
-from brick_server.minimal.descriptions import Descriptions
-from brick_server.minimal.interfaces import BaseTimeseries, GraphDB
-from brick_server.minimal.schemas import Domain, SparqlResult
-
-query_router = InferringRouter(tags=["Raw Queries"])
-
-
-@cbv(query_router)
-class TimeseriesQuery:
- ts_db: BaseTimeseries = Depends(get_ts_db)
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
-
- @query_router.post(
- "/timeseries",
- description="Raw PostgreSQL query for timeseries. (May not be exposed in the production deployment.)",
- # response_model = TimeseriesData,
- )
- async def post(
- self,
- request: Request,
- # domain: Domain = Depends(query_domain),
- query: str = Body(
- ...,
- media_type="application/sql",
- description=Descriptions.sql,
- ),
- checker: Any = Depends(PermissionChecker(PermissionType.write)),
- ):
- res = await self.ts_db.raw_query(query)
- formatted = format_raw_query(res)
- return formatted
-
-
-def timeformatter(val):
- if isinstance(val, datetime):
- return calendar.timegm(val.timetuple())
- else:
- return val
-
-
-def format_raw_query(res):
- return [tuple(timeformatter(row) for row in rows) for rows in res]
-
-
-@cbv(query_router)
-class SparqlQuery:
- auth_logic: Callable = Depends(dependency_supplier.auth_logic)
- graphdb: GraphDB = Depends(get_graphdb)
-
- @query_router.post(
- "/sparql",
- description="Raw SPARQL for Brick metadata. (May not be exposed in the production deployment.",
- )
- async def post(
- self,
- # request: Request,
- domain: Domain = Depends(query_domain),
- query: str = Body(
- ..., media_type="application/sparql-query", description=Descriptions.sparql
- ),
- checker: Any = Depends(PermissionChecker(PermissionType.write)),
- ) -> SparqlResult:
- raw_res = await self.graphdb.query(domain.name, query)
- return raw_res
diff --git a/brick_server/minimal/services/query.py b/brick_server/minimal/services/query.py
new file mode 100644
index 0000000..1359d5f
--- /dev/null
+++ b/brick_server/minimal/services/query.py
@@ -0,0 +1,78 @@
+from typing import Callable
+
+from fastapi import APIRouter, Body, Depends
+from fastapi_restful.cbv import cbv
+
+from brick_server.minimal import models, schemas
+from brick_server.minimal.interfaces import GraphDB
+from brick_server.minimal.utilities.dependencies import (
+ dependency_supplier,
+ get_graphdb,
+ get_path_domain,
+)
+from brick_server.minimal.utilities.descriptions import Descriptions
+
+router = APIRouter(tags=["queries"])
+
+
+# @cbv(query_router)
+# class TimeseriesQuery:
+# ts_db: BaseTimeseries = Depends(get_ts_db)
+# auth_logic: Callable = Depends(dependency_supplier.auth_logic)
+#
+# @query_router.post(
+# "/timeseries",
+# description="Raw PostgreSQL query for timeseries. (May not be exposed in the production deployment.)",
+# # response_model = TimeseriesData,
+# )
+# async def post(
+# self,
+# request: Request,
+# # domain: Domain = Depends(query_domain),
+# query: str = Body(
+# ...,
+# media_type="application/sql",
+# description=Descriptions.sql,
+# ),
+# checker: Any = Depends(PermissionChecker(PermissionType.WRITE)),
+# ):
+# res = await self.ts_db.raw_query(query)
+# formatted = format_raw_query(res)
+# return formatted
+#
+#
+# def timeformatter(val):
+# if isinstance(val, datetime):
+# return calendar.timegm(val.timetuple())
+# else:
+# return val
+#
+#
+# def format_raw_query(res):
+# return [tuple(timeformatter(row) for row in rows) for rows in res]
+#
+
+
+@cbv(router)
+class SparqlQuery:
+ auth_logic: Callable = Depends(dependency_supplier.auth_logic)
+ graphdb: GraphDB = Depends(get_graphdb)
+
+ @router.post(
+ "/domains/{domain}/sparql",
+ description="Raw SPARQL for Brick metadata. (May not be exposed in the production deployment.",
+ # dependencies=[
+ # Depends(PermissionChecker(permission_scope=schemas.PermissionScope.DOMAIN))
+ # ],
+ )
+ async def post(
+ self,
+ # request: Request,
+ domain: models.Domain = Depends(get_path_domain),
+ query: str = Body(
+ ..., media_type="application/sparql-query", description=Descriptions.sparql
+ ),
+ ) -> schemas.StandardResponse[dict]:
+ raw_result, prefixes = await self.graphdb.query(domain.name, query)
+ result = self.graphdb.parse_result(raw_result, prefixes)
+ return schemas.StandardResponse(result)
diff --git a/brick_server/minimal/services/user.py b/brick_server/minimal/services/user.py
new file mode 100644
index 0000000..6b73ce5
--- /dev/null
+++ b/brick_server/minimal/services/user.py
@@ -0,0 +1,162 @@
+from bson import ObjectId
+from fastapi import APIRouter, Body, Depends, Request
+from fastapi_users import (
+ BaseUserManager,
+ exceptions as fastapi_users_exceptions,
+ models as fastapi_users_models,
+)
+
+from brick_server.minimal import models, schemas
+from brick_server.minimal.securities.auth import get_token_user, get_user_manager
+from brick_server.minimal.securities.checker import PermissionChecker
+from brick_server.minimal.utilities.dependencies import get_path_user
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
+router = APIRouter(prefix="/users", tags=["users"])
+
+# router.include_router(
+# fastapi_users.get_users_router(schemas.UserRead, schemas.UserUpdate),
+# )
+
+# requires_verification = False
+# get_current_active_user = fastapi_users.authenticator.current_user(
+# active=True, verified=requires_verification
+# )
+# get_current_superuser = fastapi_users.authenticator.current_user(
+# active=True, verified=requires_verification, superuser=True
+# )
+# user_schema = schemas.UserRead
+# user_update_schema = schemas.UserUpdate
+
+
+# async def get_user_dependency(
+# user: str,
+# user_manager: BaseUserManager[
+# fastapi_users_models.UP, fastapi_users_models.ID
+# ] = Depends(get_user_manager),
+# ) -> fastapi_users_models.UP:
+# parsed_id = user_manager.parse_id(user)
+# return await user_manager.get(parsed_id)
+
+
+@router.get(
+ path="/me",
+ name="users:current_user",
+)
+async def me(
+ user: models.User = Depends(get_token_user),
+) -> schemas.StandardResponse[schemas.UserRead]:
+ return schemas.UserRead.model_validate(user).to_response()
+
+
+@router.patch(
+ path="/me",
+ name="users:patch_current_user",
+ dependencies=[
+ Depends(
+ PermissionChecker(
+ permission_scope=schemas.PermissionScope.USER,
+ )
+ ),
+ ],
+)
+async def update_me(
+ request: Request,
+ user_update: schemas.UserUpdate = Body(...),
+ user: models.User = Depends(get_token_user),
+ user_manager: BaseUserManager[
+ fastapi_users_models.UP, fastapi_users_models.ID
+ ] = Depends(get_user_manager),
+) -> schemas.StandardResponse[schemas.UserRead]:
+ if user_update.email is not None:
+ raise BizError(ErrorCode.UserNotUpdatableError, "email can not be updated")
+ if user_update.name is not None and ObjectId.is_valid(user_update.name):
+ raise BizError(ErrorCode.UserNotUpdatableError, "name can not be an ObjectId")
+ try:
+ user = await user_manager.update(user_update, user, safe=True, request=request)
+ return schemas.UserRead.model_validate(user).to_response()
+ except fastapi_users_exceptions.UserAlreadyExists:
+ raise BizError(ErrorCode.UserEmailAlreadyExistsError)
+
+
+@router.get(
+ "/{user}",
+ name="users:user",
+ dependencies=[
+ Depends(
+ PermissionChecker(
+ permission_scope=schemas.PermissionScope.USER,
+ )
+ ),
+ ],
+)
+async def get_user(
+ user: models.User = Depends(get_path_user),
+) -> schemas.StandardResponse[schemas.UserRead]:
+ return schemas.UserRead.model_validate(user).to_response()
+
+
+@router.patch(
+ "/{user}",
+ name="users:patch_user",
+ dependencies=[
+ Depends(
+ PermissionChecker(
+ permission_scope=schemas.PermissionScope.SITE,
+ )
+ ),
+ ],
+)
+async def update_user(
+ request: Request,
+ user_update: schemas.UserUpdate = Body(),
+ user: models.User = Depends(get_path_user),
+ user_manager: BaseUserManager[
+ fastapi_users_models.UP, fastapi_users_models.ID
+ ] = Depends(get_user_manager),
+) -> schemas.StandardResponse[schemas.UserRead]:
+ try:
+ user = await user_manager.update(user_update, user, safe=False, request=request)
+ return schemas.UserRead.model_validate(user).to_response()
+ except fastapi_users_exceptions.UserAlreadyExists:
+ raise BizError(ErrorCode.UserEmailAlreadyExistsError)
+
+
+@router.delete(
+ "/{user}",
+ name="users:delete_user",
+ dependencies=[
+ Depends(
+ PermissionChecker(
+ permission_scope=schemas.PermissionScope.SITE,
+ )
+ ),
+ ],
+)
+async def delete_user(
+ request: Request,
+ user: models.User = Depends(get_path_user),
+ user_manager: BaseUserManager[
+ fastapi_users_models.UP, fastapi_users_models.ID
+ ] = Depends(get_user_manager),
+) -> schemas.StandardResponse[schemas.Empty]:
+ await user_manager.delete(user, request=request)
+ return schemas.StandardResponse()
+
+
+@router.get(
+ "/",
+ name="users:list_users",
+ dependencies=[
+ Depends(
+ PermissionChecker(
+ permission_scope=schemas.PermissionScope.DOMAIN,
+ )
+ ),
+ ],
+)
+async def list_users() -> schemas.StandardListResponse[schemas.UserRead]:
+ users = await models.User.find_all().to_list()
+ return schemas.StandardListResponse(
+ [schemas.UserRead.model_validate(user.dict()) for user in users]
+ )
diff --git a/brick_server/minimal/utilities/__init__.py b/brick_server/minimal/utilities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/brick_server/minimal/models.py b/brick_server/minimal/utilities/_models.py
similarity index 87%
rename from brick_server/minimal/models.py
rename to brick_server/minimal/utilities/_models.py
index 5cf892f..a5b8145 100644
--- a/brick_server/minimal/models.py
+++ b/brick_server/minimal/utilities/_models.py
@@ -25,9 +25,6 @@ class User(DynamicDocument):
is_admin = BooleanField(default=False)
is_approved = BooleanField(default=False)
registration_time = DateTimeField(required=True)
- meta = {
- "allow_inheritance": True,
- }
app_tokens = ListField(StringField(), default=[])
@@ -59,19 +56,27 @@ class GrafanaDashboard(Document):
class Domain(Document):
name = StringField(required=True, unique=True)
+ initialized = BooleanField(required=True, default=False)
def get_doc(doc_type, **query):
try:
doc = doc_type.objects.get(**query)
except doc_type.DoesNotExist:
- print("WARNING: {} does not exist for {}".format(doc_type, query))
+ # print("WARNING: {} does not exist for {}".format(doc_type, query))
raise DoesNotExistError(doc_type, str(query))
except doc_type.MultipleObjectsReturned:
raise MultipleObjectsFoundError(doc_type, str(query))
return doc
+def get_doc_or_none(doc_type, **query):
+ try:
+ return get_doc(doc_type, **query)
+ except DoesNotExistError:
+ return None
+
+
def get_docs(doc_type, **query):
docs = doc_type.objects(**query)
return docs
diff --git a/brick_server/minimal/schemas.py b/brick_server/minimal/utilities/_schemas.py
similarity index 72%
rename from brick_server/minimal/schemas.py
rename to brick_server/minimal/utilities/_schemas.py
index 4a03357..3e1f179 100644
--- a/brick_server/minimal/schemas.py
+++ b/brick_server/minimal/utilities/_schemas.py
@@ -1,10 +1,11 @@
from copy import deepcopy
+from datetime import datetime
from enum import Enum
from typing import Any, Dict, List
-from pydantic import BaseModel, Field, conlist
+from pydantic import BaseModel, ConstrainedInt, Field, conlist
-from brick_server.minimal.descriptions import Descriptions
+from brick_server.minimal.utilities.descriptions import Descriptions
class ValueType(str, Enum):
@@ -33,6 +34,18 @@ class Relationships(BaseModel):
relationships: List[Relationship]
+class User(BaseModel):
+ class Config:
+ orm_mode = True
+
+ name: str = Field(...)
+ user_id: str = Field(...)
+ email: str = Field(...)
+ is_admin: bool = Field(False)
+ is_approved: bool = Field(False)
+ registration_time: datetime = Field(...)
+
+
class Domain(BaseModel):
class Config:
orm_mode = True
@@ -117,3 +130,48 @@ class TokenResponse(BaseModel):
TokensResponse = List[TokenResponse]
+
+
+class NoneNegativeInt(ConstrainedInt):
+ ge = 0
+
+
+class PaginationLimit(NoneNegativeInt):
+ le = 500
+
+
+class Pagination(BaseModel):
+ offset: NoneNegativeInt
+ limit: PaginationLimit
+
+
+class StrEnumMixin(str, Enum):
+ def __str__(self) -> str:
+ return self.value
+
+
+class PermissionType(StrEnumMixin, Enum):
+ READ = "read"
+ WRITE = "write"
+ NA = "na"
+
+
+class PermissionScope(StrEnumMixin, Enum):
+ SITE = "site"
+ DOMAIN = "domain"
+ ENTITY = "entity"
+ USER = "user"
+ APP = "app"
+ # ADMIN_DOMAIN = "admin_domain"
+ # ADMIN_SITE = "admin_site"
+ # ADMIN_USER = "admin_user"
+
+
+class ActuationResult(BaseModel):
+ success: bool = Field(...)
+ detail: str = Field("")
+
+
+class ActuationResults(BaseModel):
+ results: List[ActuationResult] = Field([])
+ response_time: Dict = Field({})
diff --git a/brick_server/minimal/utilities/dependencies.py b/brick_server/minimal/utilities/dependencies.py
new file mode 100644
index 0000000..d4e8edc
--- /dev/null
+++ b/brick_server/minimal/utilities/dependencies.py
@@ -0,0 +1,105 @@
+from typing import Any, Callable
+
+from bson import ObjectId
+from fastapi import Path, Query, Request
+
+from brick_server.minimal import models
+from brick_server.minimal.interfaces.actuation import actuation_iface
+from brick_server.minimal.interfaces.graphdb import GraphDB, graphdb
+from brick_server.minimal.interfaces.mongodb import AsyncDatabase, async_db
+from brick_server.minimal.interfaces.timeseries import (
+ AsyncpgTimeseries,
+ InfluxDBTimeseries,
+ influx_db,
+ timeseries_iface,
+ ts_db,
+)
+from brick_server.minimal.schemas import PaginationQuery
+from brick_server.minimal.schemas.base import PaginationLimit
+from brick_server.minimal.utilities.exceptions import BizError, ErrorCode
+
+
+class DependencySupplier:
+ # TODO: move it
+ # from brick_server.minimal.auth.authorization import PermissionType
+ # auth_logic_func_type = Callable[[Set[str], PermissionType], bool]
+ # auth_logic: Callable[[], auth_logic_func_type]
+ auth_logic: Callable[[], Any]
+
+ # def get_auth_logic(self) -> Callable[[Set[str], PermissionType], bool]:
+ # return self.auth_logic
+
+
+dependency_supplier = DependencySupplier()
+dependency_supplier.auth_logic = None
+
+
+def update_dependency_supplier(func: Callable[[], Any]):
+ dependency_supplier.auth_logic = func
+
+
+def get_auth_logic():
+ return dependency_supplier.auth_logic
+
+
+def get_graphdb() -> GraphDB:
+ return graphdb
+
+
+def get_ts_db() -> AsyncpgTimeseries:
+ return ts_db
+
+
+def get_influx_db() -> InfluxDBTimeseries:
+ return influx_db
+
+
+def get_mongodb() -> AsyncDatabase:
+ return async_db
+
+
+def get_actuation_iface():
+ return actuation_iface
+
+
+def get_timeseries_iface():
+ return timeseries_iface
+
+
+def get_pagination_query(
+ offset: int = Query(0, ge=0),
+ limit: int = Query(10, gt=0, le=PaginationLimit),
+) -> PaginationQuery:
+ return PaginationQuery(offset=offset, limit=limit)
+
+
+async def _get_domain(domain: str) -> models.Domain:
+ if ObjectId.is_valid(domain):
+ domain_model = await models.Domain.get(domain)
+ else:
+ domain_model = await models.Domain.find_one(models.Domain.name == domain)
+ return domain_model
+
+
+async def get_path_domain_optional(request: Request) -> models.Domain | None:
+ domain = request.path_params.get("domain", None)
+ if domain is None:
+ return None
+ return await _get_domain(str(domain))
+
+
+async def get_path_domain(domain: str = Path(...)) -> models.Domain:
+ domain_model = await _get_domain(domain)
+ if domain_model is None:
+ raise BizError(ErrorCode.DomainNotFoundError)
+ return domain_model
+
+
+async def get_path_user(user: str = Path(...)) -> models.User:
+ if ObjectId.is_valid(user):
+ user_model = await models.User.get(user)
+ else:
+ user_model = await models.User.find_one(models.User.name == user)
+ if user_model is None:
+ raise BizError(ErrorCode.UserNotFoundError)
+ return user_model
diff --git a/brick_server/minimal/descriptions.py b/brick_server/minimal/utilities/descriptions.py
similarity index 100%
rename from brick_server/minimal/descriptions.py
rename to brick_server/minimal/utilities/descriptions.py
diff --git a/brick_server/minimal/utilities/exceptions.py b/brick_server/minimal/utilities/exceptions.py
new file mode 100644
index 0000000..088184d
--- /dev/null
+++ b/brick_server/minimal/utilities/exceptions.py
@@ -0,0 +1,69 @@
+import inspect
+from enum import Enum
+
+from loguru import logger
+
+
+class ErrorShowType(Enum):
+ Silent = 0
+ WarnMessage = 1
+ ErrorMessage = 2
+ Notification = 3
+ Redirect = 9
+
+
+class ErrorCode(str, Enum):
+ Success = "Success"
+ Error = "Error"
+
+ # General Errors
+ UnauthorizedError = "UnauthorizedError"
+ PermissionError = "PermissionError"
+ InternalServerError = "InternalServerError"
+ InvalidTokenError = "InvalidTokenError"
+ UnknownFieldError = "UnknownFieldError"
+ IllegalFieldError = "IllegalFieldError"
+ IntegrityError = "IntegrityError"
+ ValidationError = "ValidationError"
+ ApiNotImplementedError = "ApiNotImplementedError"
+
+ # User Errors
+ UserManagerError = "UserManagerError"
+ UserNotFoundError = "UserNotFoundError"
+ UserInvalidPasswordError = "UserInvalidPasswordError"
+ UserAlreadyExistsError = "UserAlreadyExistsError"
+ UserNotUpdatableError = "UserNotUpdatableError"
+ UserEmailAlreadyExistsError = "UserEmailAlreadyExistsError"
+ UserAlreadyVerifiedError = "UserAlreadyVerifiedError"
+ UserInactiveError = "UserInactiveError"
+
+ # Domain Errors
+ DomainNotFoundError = "DomainNotFoundError"
+ DomainAlreadyExistsError = "DomainAlreadyExistsError"
+
+ # GraphDB Errors
+ GraphDBError = "GraphDBError"
+
+ # Actuation Errors
+ ActuationDriverNotFoundError = "ActuationDriverNotFoundError"
+ TimeseriesDriverNotFoundError = "TimeseriesDriverNotFoundError"
+
+
+class BizError(Exception):
+ def __init__(
+ self,
+ error_code: ErrorCode,
+ error_message: str = "",
+ show_type: ErrorShowType = ErrorShowType.ErrorMessage,
+ ):
+ self.error_code = error_code
+ self.error_message = error_message
+ self.show_type = show_type
+ try:
+ curframe = inspect.currentframe()
+ calframe = inspect.getouterframes(curframe, 2)
+ logger.info(
+ f"BizError: {calframe[1][3]} {error_code.value} {error_message}"
+ )
+ except Exception:
+ ...
diff --git a/brick_server/minimal/utilities/logging.py b/brick_server/minimal/utilities/logging.py
new file mode 100644
index 0000000..340d7ee
--- /dev/null
+++ b/brick_server/minimal/utilities/logging.py
@@ -0,0 +1,112 @@
+"""
+Configure handlers and formats for application loggers.
+
+Based on https://gist.github.com/nkhitrov/a3e31cfcc1b19cba8e1b626276148c49
+"""
+
+import logging
+import sys
+from pprint import pformat
+from typing import Any, Dict
+
+# if you dont like imports of private modules
+# you can move it to typing.py module
+from loguru import logger
+from loguru._defaults import LOGURU_FORMAT
+
+
+class InterceptHandler(logging.Handler):
+ """
+ Default handler from examples in loguru documentaion.
+ See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
+ """
+
+ def emit(self, record: logging.LogRecord) -> None:
+ # Get corresponding Loguru level if it exists
+ try:
+ level = logger.level(record.levelname).name
+ except ValueError:
+ level = record.levelno
+
+ # Find caller from where originated the logged message
+ frame, depth = logging.currentframe(), 2
+ while frame.f_code.co_filename == logging.__file__:
+ if frame.f_back is not None:
+ frame = frame.f_back
+ depth += 1
+
+ logger.opt(depth=depth, exception=record.exc_info).log(
+ level, record.getMessage()
+ )
+
+
+def format_record(record: Dict[str, Any]) -> str:
+ """
+ Custom format for loguru loggers.
+ Uses pformat for log any data like request/response body during debug.
+ Works with logging if loguru handler it.
+ Example:
+ >>> payload = [{"users":[{"name": "Nick", "age": 87, "is_active": True},
+ >>> {"name": "Alex", "age": 27, "is_active": True}], "count": 2}]
+ >>> logger.bind(payload=).debug("users payload")
+ >>> [ { 'count': 2,
+ >>> 'users': [ {'age': 87, 'is_active': True, 'name': 'Nick'},
+ >>> {'age': 27, 'is_active': True, 'name': 'Alex'}]}]
+ """
+
+ format_string = LOGURU_FORMAT
+ if record["extra"].get("payload") is not None:
+ record["extra"]["payload"] = pformat(
+ record["extra"]["payload"], indent=4, compact=True, width=88
+ )
+ format_string += "\n{extra[payload]}"
+
+ format_string += "{exception}\n"
+ return format_string
+
+
+def init_logging() -> None:
+ """
+ Replaces logging handlers with a handler for using the custom handler.
+
+ WARNING!
+ if you call the init_logging in startup event function,
+ then the first logs before the application start will be in the old format
+ >>> app.add_event_handler("startup", init_logging)
+ stdout:
+ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+ INFO: Started reloader process [11528] using statreload
+ INFO: Started server process [6036]
+ INFO: Waiting for application startup.
+ 2020-07-25 02:19:21.357 | INFO | uvicorn.lifespan.on:startup:34 - Application startup complete.
+
+ """
+
+ # disable handlers for specific uvicorn loggers
+ # to redirect their output to the default uvicorn logger
+ # works with uvicorn==0.11.6
+ loggers = (
+ logging.getLogger(name)
+ for name in logging.root.manager.loggerDict
+ if name.startswith("uvicorn.")
+ )
+ for uvicorn_logger in loggers:
+ uvicorn_logger.handlers = []
+
+ # change handler for default uvicorn logger
+ intercept_handler = InterceptHandler()
+ logging.getLogger("uvicorn").handlers = [intercept_handler]
+ logging.getLogger("uvicorn.access").handlers = [intercept_handler]
+
+ # set logs output, level and format
+ logger.configure(
+ handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}]
+ )
+ logger.disable("httpx")
+ logger.disable("httpcore")
+ logger.disable("pymongo.logger")
+
+
+def intercept_all_loggers(level: int = logging.DEBUG) -> None:
+ logging.basicConfig(handlers=[InterceptHandler()], level=level)
+ logging.getLogger("uvicorn").handlers = []
diff --git a/brick_server/minimal/utilities/utils.py b/brick_server/minimal/utilities/utils.py
new file mode 100644
index 0000000..dad5d72
--- /dev/null
+++ b/brick_server/minimal/utilities/utils.py
@@ -0,0 +1,24 @@
+from multidict import MultiDict
+
+# def parse_graphdb_result(res):
+# keys = res["head"]["vars"]
+# d = {key: [] for key in keys}
+# for row in res["results"]["bindings"]:
+# for i, key in enumerate(keys):
+# d[key].append(row[key]["value"])
+# return d
+
+
+async def get_external_references(domain, entity_id):
+ query = f"""
+select distinct ?k ?v where {{
+ <{entity_id}> ref:hasExternalReference ?o .
+ ?o ?k ?v .
+}}
+ """
+ from brick_server.minimal.interfaces.graphdb import graphdb
+
+ result, prefixes = await graphdb.query(domain.name, query)
+ parsed_result = graphdb.parse_result(result, prefixes)
+ multi_dict = MultiDict(zip(parsed_result["k"], parsed_result["v"]))
+ return multi_dict
diff --git a/demo.py b/demo.py
index 44199fd..2b2418a 100755
--- a/demo.py
+++ b/demo.py
@@ -11,16 +11,16 @@
if __name__ == "__main__":
# sparql query to find the target hvac supply flow setpoint
qstr = (
- 'select ?s where { '
- f'ebu3b:EBU3B_HVAC_Zone_Rm_{ROOM} rdf:type brick:HVAC_Zone .'
- f'?vav brick:feeds ebu3b:EBU3B_HVAC_Zone_Rm_{ROOM} .'
- '?vav rdf:type brick:VAV .'
- '?vav brick:hasPoint ?s .'
- '?s rdf:type brick:Discharge_Air_Flow_Setpoint .'
- '} limit 100'
+ "select ?s where { "
+ f"ebu3b:EBU3B_HVAC_Zone_Rm_{ROOM} rdf:type brick:HVAC_Zone ."
+ f"?vav brick:feeds ebu3b:EBU3B_HVAC_Zone_Rm_{ROOM} ."
+ "?vav rdf:type brick:VAV ."
+ "?vav brick:hasPoint ?s ."
+ "?s rdf:type brick:Discharge_Air_Flow_Setpoint ."
+ "} limit 100"
)
# example to find the airflow setpoints of all vavs on the 2nd floor
- qstr2 = '''
+ qstr2 = """
select ?s where {
?zone a brick:HVAC_Zone .
?zone brick:isPartOf ebu3b:EBU3B_Floor_2.
@@ -29,11 +29,11 @@
?vav brick:hasPoint ?s .
?s rdf:type brick:Discharge_Air_Flow_Setpoint .
} limit 100
- '''
+ """
# example to find the fan
- qstr3 = '''
+ qstr3 = """
PREFIX brick:
- select ?id where {
+ select ?id where {
?plug a brick:PlugStrip .
?plug brick:hasLocation ebu3b:EBU3B_Rm_4262 .
?filter a brick:Filter .
@@ -41,20 +41,20 @@
?s brick:isPointOf ?plug.
?s a brick:On_Off_Command .
?s brick:hasTimeseriesId ?id .
- } limit 100
- '''
+ } limit 100
+ """
headers.update({"Content-Type": "sparql-query"})
- resp = httpx.post(HOST+"/rawqueries/sparql", data=qstr3, headers=headers)
+ resp = httpx.post(HOST + "/rawqueries/sparql", data=qstr3, headers=headers)
assert len(resp.json()["results"]["bindings"]) == 1
- s = resp.json()["results"]["bindings"][0]['id']['value']
+ s = resp.json()["results"]["bindings"][0]["id"]["value"]
print(s)
-
+
# send command to actuate
# translation from brick entity_id to object identifier is not yet implemented
# using hardcoded object id now
- headers.update({'Content-Type': 'application/json'})
+ headers.update({"Content-Type": "application/json"})
body = {
- s: ['active'] # entity_id: [value, placeholder(optional)], null means revert,
+ s: ["active"] # entity_id: [value, placeholder(optional)], null means revert,
}
- resp = httpx.post(HOST+'/actuation/', json=body, headers=headers)
+ resp = httpx.post(HOST + "/actuation/", json=body, headers=headers)
assert resp.status_code == 200
diff --git a/docker-compose.yml b/docker-compose.yml
index fa1b6a1..0a808e2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -47,7 +47,12 @@ services:
volumes:
- ./brick_server:/root/brick_server
- ./tests:/root/tests
+ - ./examples:/root/examples
- /var/run/docker.sock:/var/run/docker.sock
+ build:
+ context: .
+ args:
+ DOCKER_BUILDKIT: 1
ports:
- "9000:9000"
networks:
@@ -60,7 +65,9 @@ services:
- TIMESCALE_HOST=brick-server-postgres
- BRICK_HOST=brick-server-virtuoso
- GRAPHDB_HOST=brick-server-graphdb
-# - GRAFANA_HOST=brick-server-grafana
+ - OAUTH_GOOGLE_CLIENT_ID=${OAUTH_GOOGLE_CLIENT_ID}
+ - OAUTH_GOOGLE_CLIENT_SECRET=${OAUTH_GOOGLE_CLIENT_SECRET}
+ # - GRAFANA_HOST=brick-server-grafana
depends_on:
- brick-server-graphdb
privileged: true
diff --git a/examples/data/bldg.ttl b/examples/data/bldg.ttl
index 7ba3574..ad11750 100644
--- a/examples/data/bldg.ttl
+++ b/examples/data/bldg.ttl
@@ -47,7 +47,21 @@
:BLDG_RM102_ZN_T a brick:Zone_Air_Temperature_Sensor.
:BLDG_RM102_ZNT_SP a brick:Zone_Air_Temperature_Setpoint.
:BLDG_RM102_ONOFF a brick:On_Off_Command.
-:BLDG_RM102_MTR a brick:Thermal_Power_Sensor.
+:BLDG_RM102_MTR a brick:Thermal_Power_Sensor ;
+ brick:hasUnit unit:MilliW ;
+# ref:hasExternalReference [
+# a ref:BACnetReference ;
+# bacnet:object-identifier "181160_analogInput_3" ;
+# bacnet:object-name "AI3 BERT Power Measurement" ;
+# bacnet:objectOf :plug1_BACnetDevice ; ] ;
+ ref:hasTimeseriesReference [
+ a ref:TimeseriesReference ;
+ ref:hasTimeseriesId "b67c5c50-a9bb-5a74-a399-a3b0c669373e" ;
+ ref:storedAt "influxdb://1.2.3.4:5432/db1" ; ] ;
+ ref:hasExternalReference [
+ a ref:MetasysReference ;
+ ref:metasysID "54f03333-afcb-5210-be0b-9b6996e2e76e";
+ ref:hostedAt "172.21.59.228" ] .
:RM102 brick:isLocationOf :BLDG_RM102_ZN_T.
:RM102 brick:hasPoint :BLDG_RM102_ZN_T.
diff --git a/examples/data/ebu3b_brick.ttl b/examples/data/ebu3b_brick.ttl
index 786829f..b0abf7f 100644
--- a/examples/data/ebu3b_brick.ttl
+++ b/examples/data/ebu3b_brick.ttl
@@ -6,6 +6,7 @@
@prefix xml: .
@prefix xsd: .
@prefix bacnet: .
+@prefix unit: .
ebu3b:EBU3B_Rm_4262_CORSIBOX a brick:Filter .
@@ -6068,7 +6069,22 @@ ebu3b:EBU3B_RM_2150_CLGPID_O a brick:Cooling_Command ;
brick:isPointOf ebu3b:EBU3B_VAV_Rm_2150 .
ebu3b:EBU3B_RM_2150_COMMONSP a brick:Zone_Temperature_Setpoint ;
- brick:isPointOf ebu3b:EBU3B_VAV_Rm_2150 .
+ brick:hasUnit unit:DEG_F;
+ brick:isPointOf ebu3b:EBU3B_VAV_Rm_2150 ;
+ ref:hasExternalReference [
+ a ref:BACnetReference ;
+ bacnet:object-identifier "506_analogOutput_3000493" ;
+ bacnet:object-name "NAE-06/N2-1.VMA149.COMMONSP" ;
+ bacnet:objectOf ebu3b:NAE506 ;
+ ] ;
+ ref:hasExternalReference [
+ a ref:TimeseriesReference ;
+ ref:hasTimeseriesId "506_analogOutput_3000493" ;
+ ] ;
+
+ebu3b:NAE506 a bacnet:BACnetDevice ;
+ bacnet:device-instance 506 ;
+ bacnet:hasConnectorAt "137.110.160.254:50051" .
ebu3b:EBU3B_RM_2150_DMPR_POS a brick:Damper_Position_Sensor ;
brick:isPointOf ebu3b:EBU3B_VAV_Rm_2150,
diff --git a/poetry.lock b/poetry.lock
index 34bb2e1..5218a55 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,902 +1,1871 @@
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+
+[metadata]
+content-hash = "59e3bccc8f5ee79c55d316510a57ffba17e3bc54e5cab34aed76a262fcfdd236"
+lock-version = "2.0"
+python-versions = "^3.10"
+
+[[package]]
+description = "multi backend asyncio cache"
+files = [
+ {file = "aiocache-0.12.2-py2.py3-none-any.whl", hash = "sha256:9b6fa30634ab0bfc3ecc44928a91ff07c6ea16d27d55469636b296ebc6eb5918"},
+ {file = "aiocache-0.12.2.tar.gz", hash = "sha256:b41c9a145b050a5dcbae1599f847db6dd445193b1f3bd172d8e0fe0cb9e96684"}
+]
+name = "aiocache"
+optional = false
+python-versions = "*"
+version = "0.12.2"
+
+[package.dependencies]
+redis = {markers = "extra == \"redis\"", optional = true, version = ">=4.2.0"}
+
+[package.extras]
+memcached = ["aiomcache (>=0.5.2)"]
+msgpack = ["msgpack (>=0.5.5)"]
+redis = ["redis (>=4.2.0)"]
+
+[[package]]
+description = ""
+files = [
+ {file = "aiocsv-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1996ac960c196aecc7d22e701c273a2676d13bf25575af78d4e515fc724ef20"},
+ {file = "aiocsv-1.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd688dbc1723f2b3a433e42041ceb9c9a8fe70f547d35b2da4ea31e4c78efc5"},
+ {file = "aiocsv-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f921828e386bb6945ed7d268e1524349ea506974ae35b9772542714f0ef3efd"},
+ {file = "aiocsv-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:198c905ec29897c347bf9b18eb410af13d7ac94a03d4b673e64eaa5f4557c913"},
+ {file = "aiocsv-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c25ad8afbf79d28ec3320e608c7f38d3eff93e96ebbbd2430ae8fa0f6e7631b"},
+ {file = "aiocsv-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4004569bff39cb839a335b8f673a6496fd5b0b6e074c7adb7aee4a0c8379ea22"},
+ {file = "aiocsv-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e9c98f8d760add0b52274523baa4b81dde4a3c96f79222d3d4d6965bac9cdcbd"},
+ {file = "aiocsv-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9edb342b0d7dba94d8976f46ba5814b8d8704d67a45e1b8a6579ab0ba04309e7"},
+ {file = "aiocsv-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:db943a463cb6828ba81bd7c083c6dd4c96edac4880b8638af81798d694405e26"},
+ {file = "aiocsv-1.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10780033a1ed3da825f2256449d177b7106b3c5a2d64bd683eab37f1fdee1e36"},
+ {file = "aiocsv-1.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8c7aee34ceff4eaa654f01acbdba648297f5f9532dc7a23fac62defec28e0fe5"},
+ {file = "aiocsv-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:59b0ea2d9e73539d4c1276467c4457acafa995717ea1b5340f3737f2cde2f71a"},
+ {file = "aiocsv-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1c7d1700b8de16f25b24bfcebfc2b0817b29ce413f6961f08d5aa95bf00a6862"},
+ {file = "aiocsv-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa9629c8a1c07e9d02c7d80d84f021f7994fe30d021f13ac963e251b54724ef"},
+ {file = "aiocsv-1.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d125286f971e0038e8872f31b6f1cd6184b9c508445e6633f075d8b543b444bc"},
+ {file = "aiocsv-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:b7220b4a6545abbbb6ab8fe7d4880aa8334f156b872b83641b898df2da9a6484"},
+ {file = "aiocsv-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd2ef214b6d7944991f62ac593ad45bdaf0ed9f5741c8441ee7de148e512fe7"},
+ {file = "aiocsv-1.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c3e5a817b3489283cc1fd80f8ba56431d552dc9ea4e539c0069d8d56bf0fba7"},
+ {file = "aiocsv-1.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ef14fa0839394ecc52274ea538b12b7b2e756eb0f514902a8fb391612161079"},
+ {file = "aiocsv-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:17341fa3b90414adda6cd8c79efc3c1a3f58a4dc72c2053c4532e82b61ef9f5e"},
+ {file = "aiocsv-1.3.2.tar.gz", hash = "sha256:806d93465c7808d58d3ff0d2bba270fb4d04b934be6a1e95d0834c50a510910e"}
+]
+name = "aiocsv"
+optional = false
+python-versions = ">=3.8"
+version = "1.3.2"
+
+[package.dependencies]
+typing_extensions = "*"
+
[[package]]
-name = "aiofiles"
-version = "0.7.0"
description = "File support for asyncio."
-category = "main"
+files = [
+ {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"},
+ {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}
+]
+name = "aiofiles"
optional = false
python-versions = ">=3.6,<4.0"
+version = "0.7.0"
[[package]]
-name = "aiohttp"
-version = "3.8.1"
description = "Async http client/server framework (asyncio)"
-category = "main"
+files = [
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"},
+ {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"},
+ {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"},
+ {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"},
+ {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"},
+ {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"},
+ {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"},
+ {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"},
+ {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"},
+ {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"},
+ {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"},
+ {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}
+]
+name = "aiohttp"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "3.9.5"
[package.dependencies]
aiosignal = ">=1.1.2"
-async-timeout = ">=4.0.0a3,<5.0"
-asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""}
+async-timeout = {markers = "python_version < \"3.11\"", version = ">=4.0,<5.0"}
attrs = ">=17.3.0"
-charset-normalizer = ">=2.0,<3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
-typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
yarl = ">=1.0,<2.0"
[package.extras]
-speedups = ["aiodns", "brotli", "cchardet"]
+speedups = ["aiodns", "Brotli", "brotlicffi"]
[[package]]
-name = "aiosignal"
-version = "1.2.0"
description = "aiosignal: a list of registered asynchronous callbacks"
-category = "main"
+files = [
+ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
+ {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}
+]
+name = "aiosignal"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "1.3.1"
[package.dependencies]
frozenlist = ">=1.1.0"
[[package]]
-name = "aiosparql"
-version = "0.12.0"
-description = "An asynchronous SPARQL library using aiohttp"
-category = "main"
+description = "Reusable constraint types to use with typing.Annotated"
+files = [
+ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
+ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}
+]
+name = "annotated-types"
optional = false
-python-versions = ">=3.6.0"
-
-[package.dependencies]
-aiohttp = ">=3.5.0"
+python-versions = ">=3.8"
+version = "0.6.0"
[[package]]
-name = "anyio"
-version = "3.6.1"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "main"
+files = [
+ {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
+ {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}
+]
+name = "anyio"
optional = false
-python-versions = ">=3.6.2"
+python-versions = ">=3.8"
+version = "4.3.0"
[package.dependencies]
+exceptiongroup = {markers = "python_version < \"3.11\"", version = ">=1.0.2"}
idna = ">=2.8"
sniffio = ">=1.1"
-typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
+typing-extensions = {markers = "python_version < \"3.11\"", version = ">=4.1"}
[package.extras]
-doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
-test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
-trio = ["trio (>=0.16)"]
+doc = ["packaging", "Sphinx (>=7)", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (>=0.23)"]
[[package]]
-name = "arrow"
-version = "1.2.1"
-description = "Better dates & times for Python"
-category = "main"
+description = "Argon2 for Python"
+files = [
+ {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"},
+ {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}
+]
+name = "argon2-cffi"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "23.1.0"
[package.dependencies]
-python-dateutil = ">=2.7.0"
-typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
+argon2-cffi-bindings = "*"
-[[package]]
-name = "asgi-lifespan"
-version = "1.0.1"
-description = "Programmatic startup/shutdown of ASGI apps."
-category = "dev"
+[package.extras]
+dev = ["argon2-cffi[tests,typing]", "tox (>4)"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"]
+tests = ["hypothesis", "pytest"]
+typing = ["mypy"]
+
+[[package]]
+description = "Low-level CFFI bindings for Argon2"
+files = [
+ {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"}
+]
+name = "argon2-cffi-bindings"
optional = false
python-versions = ">=3.6"
+version = "21.2.0"
[package.dependencies]
-sniffio = "*"
+cffi = ">=1.0.1"
+
+[package.extras]
+dev = ["cogapp", "pre-commit", "pytest", "wheel"]
+tests = ["pytest"]
[[package]]
-name = "asgiref"
-version = "3.4.1"
-description = "ASGI specs, helper code, and adapters"
-category = "main"
+description = "Better dates & times for Python"
+files = [
+ {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"},
+ {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}
+]
+name = "arrow"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "1.3.0"
[package.dependencies]
-typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
+python-dateutil = ">=2.7.0"
+types-python-dateutil = ">=2.8.10"
[package.extras]
-tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
+doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"]
+test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"]
[[package]]
-name = "async-timeout"
-version = "4.0.1"
-description = "Timeout context manager for asyncio programs"
-category = "main"
+description = "Programmatic startup/shutdown of ASGI apps."
+files = [
+ {file = "asgi-lifespan-1.0.1.tar.gz", hash = "sha256:9a33e7da2073c4764bc79bd6136501d6c42f60e3d2168ba71235e84122eadb7f"},
+ {file = "asgi_lifespan-1.0.1-py3-none-any.whl", hash = "sha256:9ea969dc5eb5cf08e52c08dce6f61afcadd28112e72d81c972b1d8eb8691ab53"}
+]
+name = "asgi-lifespan"
optional = false
python-versions = ">=3.6"
+version = "1.0.1"
[package.dependencies]
-typing-extensions = ">=3.6.5"
+sniffio = "*"
+
+[[package]]
+description = "Timeout context manager for asyncio programs"
+files = [
+ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
+ {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}
+]
+name = "async-timeout"
+optional = false
+python-versions = ">=3.7"
+version = "4.0.3"
[[package]]
-name = "asyncpg"
-version = "0.24.0"
description = "An asyncio PostgreSQL driver"
-category = "main"
+files = [
+ {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"},
+ {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"},
+ {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"},
+ {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"},
+ {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"},
+ {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"},
+ {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"},
+ {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"},
+ {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"},
+ {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"},
+ {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"},
+ {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"},
+ {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"},
+ {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"},
+ {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"},
+ {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"},
+ {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"},
+ {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"},
+ {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"},
+ {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"},
+ {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"},
+ {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"},
+ {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"},
+ {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"},
+ {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"},
+ {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"},
+ {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"},
+ {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"},
+ {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"},
+ {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"},
+ {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"},
+ {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"},
+ {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"},
+ {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"},
+ {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"},
+ {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"},
+ {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"},
+ {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"},
+ {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"},
+ {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"},
+ {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}
+]
+name = "asyncpg"
optional = false
-python-versions = ">=3.6.0"
+python-versions = ">=3.8.0"
+version = "0.29.0"
[package.dependencies]
-typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""}
+async-timeout = {markers = "python_version < \"3.12.0\"", version = ">=4.0.3"}
[package.extras]
-dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"]
-docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
-test = ["pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"]
+docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
[[package]]
-name = "asynctest"
-version = "0.13.0"
-description = "Enhance the standard unittest package with features for testing asyncio libraries"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[[package]]
-name = "atomicwrites"
-version = "1.4.0"
description = "Atomic file writes."
-category = "dev"
+files = [
+ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}
+]
+name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.4.1"
[[package]]
-name = "attrs"
-version = "21.2.0"
description = "Classes Without Boilerplate"
-category = "main"
+files = [
+ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
+ {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}
+]
+name = "attrs"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.7"
+version = "23.2.0"
[package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
-docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
-
-[[package]]
-name = "authlib"
-version = "0.15.5"
-description = "The ultimate Python library in building OAuth and OpenID Connect servers."
-category = "main"
+cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
+dev = ["attrs[tests]", "pre-commit"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
+tests = ["attrs[tests-no-zope]", "zope-interface"]
+tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
+tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
+
+[[package]]
+description = "Modern password hashing for your software and your servers"
+files = [
+ {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"},
+ {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"},
+ {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"},
+ {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"},
+ {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"},
+ {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"},
+ {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"},
+ {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"},
+ {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"},
+ {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"},
+ {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"},
+ {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"},
+ {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"},
+ {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"},
+ {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"},
+ {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"},
+ {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"},
+ {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"},
+ {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"},
+ {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"},
+ {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"},
+ {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"},
+ {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"},
+ {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"},
+ {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"},
+ {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"},
+ {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}
+]
+name = "bcrypt"
optional = false
-python-versions = "*"
-
-[package.dependencies]
-cryptography = "*"
+python-versions = ">=3.7"
+version = "4.1.2"
[package.extras]
-client = ["requests"]
+tests = ["pytest (>=3.2.1,!=3.3.0)"]
+typecheck = ["mypy"]
[[package]]
-name = "backports.entry-points-selectable"
-version = "1.1.1"
-description = "Compatibility shim providing selectable entry points for older implementations"
-category = "dev"
+description = "Asynchronous Python ODM for MongoDB"
+files = [
+ {file = "beanie-1.26.0-py3-none-any.whl", hash = "sha256:b45926c01d4a899c519c665c2a5f230990717e99f7fd68172a389ca33e7693b9"},
+ {file = "beanie-1.26.0.tar.gz", hash = "sha256:54016f4ec71ed0ea6ce0c7946a395090c45687f254dbbe1cf06eec608383f790"}
+]
+name = "beanie"
optional = false
-python-versions = ">=2.7"
+python-versions = "<4.0,>=3.7"
+version = "1.26.0"
[package.dependencies]
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+click = ">=7"
+lazy-model = "0.2.0"
+motor = ">=2.5.0,<4.0.0"
+pydantic = ">=1.10,<3.0"
+toml = "*"
+typing-extensions = {markers = "python_version < \"3.11\"", version = ">=4.7"}
[package.extras]
-testing = ["pytest-enabler (>=1.0.1)", "pytest-checkdocs (>=2.4)", "pytest-mypy", "pytest-black (>=0.3.7)", "pytest-cov", "pytest-flake8", "pytest"]
-docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"]
-
-[[package]]
-name = "brick-data"
-version = "0.0.1"
-description = "A wrapper of Timeseries DBs"
-category = "main"
+doc = ["jinja2 (>=3.0.3)", "Markdown (>=3.3)", "mkdocs (>=1.4)", "mkdocs-material (>=9.0)", "pydoc-markdown (>=4.8)", "Pygments (>=2.8.0)"]
+queue = ["beanie-batteries-queue (>=0.2)"]
+test = ["asgi-lifespan (>=1.0.1)", "dnspython (>=2.1.0)", "fastapi (>=0.100)", "flake8 (>=3)", "httpx (>=0.23.0)", "pre-commit (>=2.3.0)", "pydantic-extra-types (>=2)", "pydantic-settings (>=2)", "pydantic[email]", "pyright (>=0)", "pytest (>=6.0.0)", "pytest-asyncio (>=0.21.0)", "pytest-cov (>=2.8.1)"]
+
+[[package]]
+description = "The uncompromising code formatter."
+files = [
+ {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
+ {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
+ {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
+ {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
+ {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
+ {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
+ {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
+ {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
+ {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
+ {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
+ {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
+ {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
+ {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
+ {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
+ {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
+ {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
+ {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
+ {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
+ {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
+ {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
+ {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
+ {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}
+]
+name = "black"
optional = false
-python-versions = "*"
-develop = false
+python-versions = ">=3.8"
+version = "24.4.2"
[package.dependencies]
-aiofiles = "*"
-aiosparql = "*"
-arrow = "*"
-asyncpg = "*"
-geoalchemy2 = "*"
-moz-sql-parser = "*"
-pandas = "*"
-psycopg2-binary = "*"
-rdflib = "*"
-semver = "*"
-shapely = "*"
-SPARQLWrapper = "*"
-sqlalchemy = "*"
-validators = "*"
-
-[package.source]
-type = "git"
-url = "https://github.com/jbkoh/brick_data.git"
-reference = "master"
-resolved_reference = "bab392b8b0b83c7a0c5427c08f3d9f2b22a8ab06"
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {markers = "python_version < \"3.11\"", version = ">=1.1.0"}
+typing-extensions = {markers = "python_version < \"3.11\"", version = ">=4.0.1"}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
[[package]]
-name = "certifi"
-version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
+files = [
+ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}
+]
+name = "certifi"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
+version = "2024.2.2"
[[package]]
-name = "cffi"
-version = "1.15.0"
description = "Foreign Function Interface for Python calling C code."
-category = "main"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}
+]
+name = "cffi"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
+version = "1.16.0"
[package.dependencies]
pycparser = "*"
[[package]]
-name = "cfgv"
-version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
-category = "dev"
-optional = false
-python-versions = ">=3.6.1"
-
-[[package]]
-name = "charset-normalizer"
-version = "2.0.9"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
+files = [
+ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}
+]
+name = "cfgv"
optional = false
-python-versions = ">=3.5.0"
-
-[package.extras]
-unicode_backport = ["unicodedata2"]
+python-versions = ">=3.8"
+version = "3.4.0"
[[package]]
-name = "click"
-version = "7.1.2"
description = "Composable command line interface toolkit"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "click-default-group"
-version = "1.2.2"
-description = "Extends click.Group to invoke a command without explicit subcommand name"
-category = "main"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}
+]
+name = "click"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+version = "8.1.7"
[package.dependencies]
-click = "*"
+colorama = {markers = "platform_system == \"Windows\"", version = "*"}
[[package]]
-name = "click-option-group"
-version = "0.5.3"
-description = "Option groups missing in Click"
-category = "main"
+description = "click_default_group"
+files = [
+ {file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"},
+ {file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"}
+]
+name = "click-default-group"
optional = false
-python-versions = ">=3.6,<4"
+python-versions = ">=2.7"
+version = "1.2.4"
[package.dependencies]
-Click = ">=7.0,<9"
+click = "*"
[package.extras]
-tests = ["coveralls", "pytest-cov", "pytest", "coverage (<6)"]
-docs = ["m2r", "pallets-sphinx-themes", "sphinx (>=2.3,<3)"]
+test = ["pytest"]
[[package]]
-name = "colorama"
-version = "0.4.4"
description = "Cross-platform colored terminal text."
-category = "main"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}
+]
+name = "colorama"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+version = "0.4.6"
[[package]]
-name = "cryptography"
-version = "36.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
-category = "main"
+files = [
+ {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"},
+ {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"},
+ {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"},
+ {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"},
+ {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"},
+ {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"},
+ {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"},
+ {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}
+]
+name = "cryptography"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "42.0.7"
[package.dependencies]
-cffi = ">=1.12"
+cffi = {markers = "platform_python_implementation != \"PyPy\"", version = ">=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)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
-pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
-sdist = ["setuptools_rust (>=0.11.4)"]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
+docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
+nox = ["nox"]
+pep8test = ["check-sdist", "click", "mypy", "ruff"]
+sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"]
-test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
+test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
+test-randomorder = ["pytest-randomly"]
[[package]]
-name = "decorator"
-version = "5.1.0"
-description = "Decorators for Humans"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[[package]]
-name = "distlib"
-version = "0.3.3"
description = "Distribution utilities"
-category = "dev"
+files = [
+ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
+ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}
+]
+name = "distlib"
optional = false
python-versions = "*"
+version = "0.3.8"
[[package]]
-name = "fastapi"
-version = "0.78.0"
-description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
-category = "main"
+description = "DNS toolkit"
+files = [
+ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
+ {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}
+]
+name = "dnspython"
+optional = false
+python-versions = ">=3.8"
+version = "2.6.1"
+
+[package.extras]
+dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
+dnssec = ["cryptography (>=41)"]
+doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
+doq = ["aioquic (>=0.9.25)"]
+idna = ["idna (>=3.6)"]
+trio = ["trio (>=0.23)"]
+wmi = ["wmi (>=1.5.1)"]
+
+[[package]]
+description = "A robust email address syntax and deliverability validation library."
+files = [
+ {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"},
+ {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}
+]
+name = "email-validator"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.8"
+version = "2.1.1"
[package.dependencies]
-pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
-starlette = "0.19.1"
+dnspython = ">=2.0.0"
+idna = ">=2.0.0"
+
+[[package]]
+description = "Backport of PEP 654 (exception groups)"
+files = [
+ {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
+ {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}
+]
+name = "exceptiongroup"
+optional = false
+python-versions = ">=3.7"
+version = "1.2.1"
[package.extras]
-all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
-dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"]
-doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"]
-test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"]
+test = ["pytest (>=6)"]
[[package]]
-name = "fastapi-rest-framework"
-version = "0.0.1"
-description = ""
-category = "main"
+description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+files = [
+ {file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"},
+ {file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"}
+]
+name = "fastapi"
optional = false
-python-versions = "^3.7"
-develop = false
+python-versions = ">=3.8"
+version = "0.110.3"
[package.dependencies]
-click = "^7.1.2"
-click-option-group = "^0.5.3"
-loguru = "^0.5.3"
-makefun = "^1.11.3"
-psutil = "^5.8.0"
-pydantic = {version = "^1.8.2", extras = ["dotenv"]}
+pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
+starlette = ">=0.37.2,<0.38.0"
+typing-extensions = ">=4.8.0"
[package.extras]
-fastapi = ["fastapi (>=0.70.0,<0.71.0)"]
-
-[package.source]
-type = "git"
-url = "https://github.com/joint-online-judge/fastapi-rest-framework"
-reference = "master"
-resolved_reference = "beae1e1e0b415ccbd933e6f319b31299941bbfc2"
+all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
-name = "fastapi-utils"
-version = "0.2.1"
-description = "Reusable utilities for FastAPI"
-category = "main"
+description = "Quicker FastApi developing tools"
+files = [
+ {file = "fastapi_restful-0.5.0-py3-none-any.whl", hash = "sha256:f768bfe383fe9ef4affe357572122c8348d6a108eef5e373101c8cde18c0d27d"},
+ {file = "fastapi_restful-0.5.0.tar.gz", hash = "sha256:f4215d262aa3fb3d6024e1b45061151f3e38afa41877c6253f434c65690c1ed7"}
+]
+name = "fastapi-restful"
optional = false
-python-versions = ">=3.6,<4.0"
+python-versions = ">=3.7,<4.0"
+version = "0.5.0"
[package.dependencies]
-fastapi = "*"
-pydantic = ">=1.0,<2.0"
-sqlalchemy = ">=1.3.12,<2.0.0"
+fastapi = ">=0.89,<1.0"
+psutil = ">=5,<6"
+pydantic = ">1.0,<3.0"
+
+[package.extras]
+all = ["pydantic-settings (>=2.0.1,<3.0.0)", "sqlalchemy (>=1.4,<3.0)", "typing-inspect (>=0.9.0,<0.10.0)"]
+session = ["sqlalchemy (>=1.4,<3.0)"]
[[package]]
-name = "filelock"
-version = "3.4.0"
-description = "A platform independent file lock."
-category = "dev"
+description = "Ready-to-use and customizable users management for FastAPI"
+files = [
+ {file = "fastapi_users-13.0.0-py3-none-any.whl", hash = "sha256:e6246529e3080a5b50e5afeed1e996663b661f1dc791a1ac478925cb5bfc0fa0"},
+ {file = "fastapi_users-13.0.0.tar.gz", hash = "sha256:b397c815b7051c8fd4b560fbeee707acd28e00bd3e8f25c292ad158a1e47e884"}
+]
+name = "fastapi-users"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "13.0.0"
+
+[package.dependencies]
+email-validator = ">=1.1.0,<2.2"
+fastapi = ">=0.65.2"
+fastapi-users-db-beanie = {markers = "extra == \"beanie\"", optional = true, version = ">=3.0.0"}
+httpx-oauth = {markers = "extra == \"oauth\"", optional = true, version = ">=0.13"}
+makefun = ">=1.11.2,<2.0.0"
+pwdlib = {extras = ["argon2", "bcrypt"], version = "0.2.0"}
+pyjwt = {extras = ["crypto"], version = "2.8.0"}
+python-multipart = "0.0.9"
[package.extras]
-docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
-testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
+beanie = ["fastapi-users-db-beanie (>=3.0.0)"]
+oauth = ["httpx-oauth (>=0.13)"]
+redis = ["redis (>=4.3.3,<6.0.0)"]
+sqlalchemy = ["fastapi-users-db-sqlalchemy (>=6.0.0)"]
[[package]]
-name = "frozenlist"
-version = "1.2.0"
-description = "A list-like structure which implements collections.abc.MutableSequence"
-category = "main"
+description = "FastAPI Users database adapter for Beanie"
+files = [
+ {file = "fastapi_users_db_beanie-3.0.0-py3-none-any.whl", hash = "sha256:4ed99cca9565cfa67bed436def0b27705142230409516915448494fbfc0fb031"},
+ {file = "fastapi_users_db_beanie-3.0.0.tar.gz", hash = "sha256:5f8000d394ddc065a5eaf7244aa2ac3603acd6a2184d12fa6fa5237b78126369"}
+]
+name = "fastapi-users-db-beanie"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "3.0.0"
+
+[package.dependencies]
+beanie = ">=1.11.0,<2.0.0"
+fastapi-users = ">=10.0.1"
[[package]]
-name = "future-fstrings"
-version = "1.2.0"
-description = "A backport of fstrings to python<3.6"
-category = "dev"
+description = "A platform independent file lock."
+files = [
+ {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
+ {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}
+]
+name = "filelock"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.8"
+version = "3.14.0"
[package.extras]
-rewrite = ["tokenize-rt (>=3)"]
+docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+typing = ["typing-extensions (>=4.8)"]
[[package]]
-name = "geoalchemy2"
-version = "0.9.4"
-description = "Using SQLAlchemy with Spatial Databases"
-category = "main"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+files = [
+ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"},
+ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"},
+ {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"},
+ {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"},
+ {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"},
+ {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"},
+ {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"},
+ {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"},
+ {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"},
+ {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"},
+ {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"},
+ {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"},
+ {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"},
+ {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"},
+ {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"},
+ {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"},
+ {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"},
+ {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"},
+ {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"},
+ {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"},
+ {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"},
+ {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"},
+ {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"},
+ {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"},
+ {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"},
+ {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"},
+ {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"},
+ {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"},
+ {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"},
+ {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"},
+ {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"},
+ {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"},
+ {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"},
+ {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"},
+ {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"},
+ {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"},
+ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}
+]
+name = "frozenlist"
optional = false
-python-versions = "*"
-
-[package.dependencies]
-packaging = "*"
-SQLAlchemy = ">=1.1"
+python-versions = ">=3.8"
+version = "1.4.1"
[[package]]
-name = "greenlet"
-version = "1.1.2"
-description = "Lightweight in-process concurrent programming"
-category = "main"
+description = "A backport of fstrings to python<3.6"
+files = [
+ {file = "future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63"},
+ {file = "future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089"}
+]
+name = "future-fstrings"
optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.2.0"
[package.extras]
-docs = ["sphinx"]
+rewrite = ["tokenize-rt (>=3)"]
[[package]]
-name = "grpcio"
-version = "1.42.0"
description = "HTTP/2-based RPC framework"
-category = "main"
+files = [
+ {file = "grpcio-1.63.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2e93aca840c29d4ab5db93f94ed0a0ca899e241f2e8aec6334ab3575dc46125c"},
+ {file = "grpcio-1.63.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:91b73d3f1340fefa1e1716c8c1ec9930c676d6b10a3513ab6c26004cb02d8b3f"},
+ {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b3afbd9d6827fa6f475a4f91db55e441113f6d3eb9b7ebb8fb806e5bb6d6bd0d"},
+ {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f3f6883ce54a7a5f47db43289a0a4c776487912de1a0e2cc83fdaec9685cc9f"},
+ {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf8dae9cc0412cb86c8de5a8f3be395c5119a370f3ce2e69c8b7d46bb9872c8d"},
+ {file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08e1559fd3b3b4468486b26b0af64a3904a8dbc78d8d936af9c1cf9636eb3e8b"},
+ {file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c039ef01516039fa39da8a8a43a95b64e288f79f42a17e6c2904a02a319b357"},
+ {file = "grpcio-1.63.0-cp310-cp310-win32.whl", hash = "sha256:ad2ac8903b2eae071055a927ef74121ed52d69468e91d9bcbd028bd0e554be6d"},
+ {file = "grpcio-1.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2e44f59316716532a993ca2966636df6fbe7be4ab6f099de6815570ebe4383a"},
+ {file = "grpcio-1.63.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f28f8b2db7b86c77916829d64ab21ff49a9d8289ea1564a2b2a3a8ed9ffcccd3"},
+ {file = "grpcio-1.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:65bf975639a1f93bee63ca60d2e4951f1b543f498d581869922910a476ead2f5"},
+ {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5194775fec7dc3dbd6a935102bb156cd2c35efe1685b0a46c67b927c74f0cfb"},
+ {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4cbb2100ee46d024c45920d16e888ee5d3cf47c66e316210bc236d5bebc42b3"},
+ {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff737cf29b5b801619f10e59b581869e32f400159e8b12d7a97e7e3bdeee6a2"},
+ {file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd1e68776262dd44dedd7381b1a0ad09d9930ffb405f737d64f505eb7f77d6c7"},
+ {file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f45f27f516548e23e4ec3fbab21b060416007dbe768a111fc4611464cc773f"},
+ {file = "grpcio-1.63.0-cp311-cp311-win32.whl", hash = "sha256:878b1d88d0137df60e6b09b74cdb73db123f9579232c8456f53e9abc4f62eb3c"},
+ {file = "grpcio-1.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:756fed02dacd24e8f488f295a913f250b56b98fb793f41d5b2de6c44fb762434"},
+ {file = "grpcio-1.63.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:93a46794cc96c3a674cdfb59ef9ce84d46185fe9421baf2268ccb556f8f81f57"},
+ {file = "grpcio-1.63.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a7b19dfc74d0be7032ca1eda0ed545e582ee46cd65c162f9e9fc6b26ef827dc6"},
+ {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:8064d986d3a64ba21e498b9a376cbc5d6ab2e8ab0e288d39f266f0fca169b90d"},
+ {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:219bb1848cd2c90348c79ed0a6b0ea51866bc7e72fa6e205e459fedab5770172"},
+ {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d60cd1d58817bc5985fae6168d8b5655c4981d448d0f5b6194bbcc038090d2"},
+ {file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e350cb096e5c67832e9b6e018cf8a0d2a53b2a958f6251615173165269a91b0"},
+ {file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:56cdf96ff82e3cc90dbe8bac260352993f23e8e256e063c327b6cf9c88daf7a9"},
+ {file = "grpcio-1.63.0-cp312-cp312-win32.whl", hash = "sha256:3a6d1f9ea965e750db7b4ee6f9fdef5fdf135abe8a249e75d84b0a3e0c668a1b"},
+ {file = "grpcio-1.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:d2497769895bb03efe3187fb1888fc20e98a5f18b3d14b606167dacda5789434"},
+ {file = "grpcio-1.63.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fdf348ae69c6ff484402cfdb14e18c1b0054ac2420079d575c53a60b9b2853ae"},
+ {file = "grpcio-1.63.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a3abfe0b0f6798dedd2e9e92e881d9acd0fdb62ae27dcbbfa7654a57e24060c0"},
+ {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:6ef0ad92873672a2a3767cb827b64741c363ebaa27e7f21659e4e31f4d750280"},
+ {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b416252ac5588d9dfb8a30a191451adbf534e9ce5f56bb02cd193f12d8845b7f"},
+ {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b77eaefc74d7eb861d3ffbdf91b50a1bb1639514ebe764c47773b833fa2d91"},
+ {file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b005292369d9c1f80bf70c1db1c17c6c342da7576f1c689e8eee4fb0c256af85"},
+ {file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdcda1156dcc41e042d1e899ba1f5c2e9f3cd7625b3d6ebfa619806a4c1aadda"},
+ {file = "grpcio-1.63.0-cp38-cp38-win32.whl", hash = "sha256:01799e8649f9e94ba7db1aeb3452188048b0019dc37696b0f5ce212c87c560c3"},
+ {file = "grpcio-1.63.0-cp38-cp38-win_amd64.whl", hash = "sha256:6a1a3642d76f887aa4009d92f71eb37809abceb3b7b5a1eec9c554a246f20e3a"},
+ {file = "grpcio-1.63.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:75f701ff645858a2b16bc8c9fc68af215a8bb2d5a9b647448129de6e85d52bce"},
+ {file = "grpcio-1.63.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cacdef0348a08e475a721967f48206a2254a1b26ee7637638d9e081761a5ba86"},
+ {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:0697563d1d84d6985e40ec5ec596ff41b52abb3fd91ec240e8cb44a63b895094"},
+ {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6426e1fb92d006e47476d42b8f240c1d916a6d4423c5258ccc5b105e43438f61"},
+ {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48cee31bc5f5a31fb2f3b573764bd563aaa5472342860edcc7039525b53e46a"},
+ {file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:50344663068041b34a992c19c600236e7abb42d6ec32567916b87b4c8b8833b3"},
+ {file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:259e11932230d70ef24a21b9fb5bb947eb4703f57865a404054400ee92f42f5d"},
+ {file = "grpcio-1.63.0-cp39-cp39-win32.whl", hash = "sha256:a44624aad77bf8ca198c55af811fd28f2b3eaf0a50ec5b57b06c034416ef2d0a"},
+ {file = "grpcio-1.63.0-cp39-cp39-win_amd64.whl", hash = "sha256:166e5c460e5d7d4656ff9e63b13e1f6029b122104c1633d5f37eaea348d7356d"},
+ {file = "grpcio-1.63.0.tar.gz", hash = "sha256:f3023e14805c61bc439fb40ca545ac3d5740ce66120a678a3c6c2c55b70343d1"}
+]
+name = "grpcio"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-six = ">=1.5.2"
+python-versions = ">=3.8"
+version = "1.63.0"
[package.extras]
-protobuf = ["grpcio-tools (>=1.42.0)"]
+protobuf = ["grpcio-tools (>=1.63.0)"]
[[package]]
-name = "h11"
-version = "0.12.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "main"
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}
+]
+name = "h11"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "0.14.0"
[[package]]
-name = "httpcore"
-version = "0.15.0"
description = "A minimal low-level HTTP client."
-category = "main"
+files = [
+ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
+ {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}
+]
+name = "httpcore"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+version = "1.0.5"
[package.dependencies]
-anyio = ">=3.0.0,<4.0.0"
certifi = "*"
-h11 = ">=0.11,<0.13"
-sniffio = ">=1.0.0,<2.0.0"
+h11 = ">=0.13,<0.15"
[package.extras]
+asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
+trio = ["trio (>=0.22.0,<0.26.0)"]
+
+[[package]]
+description = "A collection of framework independent HTTP protocol utils."
+files = [
+ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
+ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
+ {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"},
+ {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"},
+ {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"},
+ {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"},
+ {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"},
+ {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"},
+ {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"},
+ {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"},
+ {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"},
+ {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"},
+ {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"},
+ {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"},
+ {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"},
+ {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"},
+ {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"},
+ {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"},
+ {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"},
+ {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"},
+ {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"},
+ {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"},
+ {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"},
+ {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"},
+ {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"},
+ {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"},
+ {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"},
+ {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"},
+ {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"},
+ {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"},
+ {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"},
+ {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"},
+ {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"},
+ {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"},
+ {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"},
+ {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}
+]
+name = "httptools"
+optional = false
+python-versions = ">=3.8.0"
+version = "0.6.1"
+
+[package.extras]
+test = ["Cython (>=0.29.24,<0.30.0)"]
[[package]]
-name = "httpx"
-version = "0.23.0"
description = "The next generation HTTP client."
-category = "main"
+files = [
+ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
+ {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}
+]
+name = "httpx"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+version = "0.27.0"
[package.dependencies]
+anyio = "*"
certifi = "*"
-httpcore = ">=0.15.0,<0.16.0"
-rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
+httpcore = "==1.*"
+idna = "*"
sniffio = "*"
[package.extras]
-brotli = ["brotlicffi", "brotli"]
-cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
+
+[[package]]
+description = "Async OAuth client using HTTPX"
+files = [
+ {file = "httpx_oauth-0.14.1-py3-none-any.whl", hash = "sha256:3ccf35760d157a9abbf4a78024b744b5532ec5d4d03fc1db3e71123ce9d33e32"},
+ {file = "httpx_oauth-0.14.1.tar.gz", hash = "sha256:e58c60bce99568b4e98ce88abf0e2111a9b6c7f79c29bea88cc89d9edb78dfbc"}
+]
+name = "httpx-oauth"
+optional = false
+python-versions = ">=3.8"
+version = "0.14.1"
+
+[package.dependencies]
+httpx = ">=0.18,<1.0.0"
[[package]]
-name = "identify"
-version = "2.4.0"
description = "File identification library for Python"
-category = "dev"
+files = [
+ {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"},
+ {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}
+]
+name = "identify"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.8"
+version = "2.5.36"
[package.extras]
license = ["ukkonen"]
[[package]]
-name = "idna"
-version = "2.10"
description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
+files = [
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}
+]
+name = "idna"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.5"
+version = "3.7"
[[package]]
-name = "importlib-metadata"
-version = "4.8.2"
-description = "Read metadata from Python packages"
-category = "main"
+description = "InfluxDB 2.0 Python client library"
+files = [
+ {file = "influxdb_client-1.42.0-py3-none-any.whl", hash = "sha256:0161b963f221d5c1769202f41ff55f5d79e00e6dc24e2a0729c82a1e131956ee"},
+ {file = "influxdb_client-1.42.0.tar.gz", hash = "sha256:f5e877feb671eda41e2b5c98ed1dc8ec3327fd8991360dc614822119cda06491"}
+]
+name = "influxdb-client"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "1.42.0"
[package.dependencies]
-typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
-zipp = ">=0.5"
+aiocsv = {markers = "extra == \"async\"", optional = true, version = ">=1.2.2"}
+aiohttp = {markers = "extra == \"async\"", optional = true, version = ">=3.8.1"}
+certifi = ">=14.05.14"
+python-dateutil = ">=2.5.3"
+reactivex = ">=4.0.4"
+setuptools = ">=21.0.0"
+urllib3 = ">=1.26.0"
[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+async = ["aiocsv (>=1.2.2)", "aiohttp (>=3.8.1)"]
+ciso = ["ciso8601 (>=2.1.1)"]
+extra = ["numpy", "pandas (>=1.0.0)"]
+test = ["aioresponses (>=0.7.3)", "coverage (>=4.0.3)", "flake8 (>=5.0.3)", "httpretty (==1.0.5)", "jinja2 (==3.1.3)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "psutil (>=5.6.3)", "py (>=1.4.31)", "pytest (>=5.0.0)", "pytest-cov (>=3.0.0)", "pytest-timeout (>=2.1.0)", "randomize (>=0.13)", "sphinx (==1.8.5)", "sphinx-rtd-theme"]
[[package]]
+description = "brain-dead simple config-ini parsing"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}
+]
name = "iniconfig"
-version = "1.1.1"
-description = "iniconfig: brain-dead simple config-ini parsing"
-category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+version = "2.0.0"
[[package]]
-name = "isodate"
-version = "0.6.0"
-description = "An ISO 8601 date/time/duration parser and formatter"
-category = "main"
+description = ""
+files = [
+ {file = "lazy-model-0.2.0.tar.gz", hash = "sha256:57c0e91e171530c4fca7aebc3ac05a163a85cddd941bf7527cc46c0ddafca47c"},
+ {file = "lazy_model-0.2.0-py3-none-any.whl", hash = "sha256:5a3241775c253e36d9069d236be8378288a93d4fc53805211fd152e04cc9c342"}
+]
+name = "lazy-model"
optional = false
-python-versions = "*"
+python-versions = ">=3.7,<4.0"
+version = "0.2.0"
[package.dependencies]
-six = "*"
-
-[[package]]
-name = "itsdangerous"
-version = "2.0.1"
-description = "Safely pass data to untrusted environments and back."
-category = "main"
-optional = false
-python-versions = ">=3.6"
+pydantic = ">=1.9.0"
[[package]]
-name = "loguru"
-version = "0.5.3"
description = "Python logging made (stupidly) simple"
-category = "main"
+files = [
+ {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
+ {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}
+]
+name = "loguru"
optional = false
python-versions = ">=3.5"
+version = "0.7.2"
[package.dependencies]
-colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
-win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
+colorama = {markers = "sys_platform == \"win32\"", version = ">=0.3.4"}
+win32-setctime = {markers = "sys_platform == \"win32\"", version = ">=1.0.0"}
[package.extras]
-dev = ["isort (>=5.1.1)", "black (>=19.10b0)", "sphinx-rtd-theme (>=0.4.3)", "sphinx-autobuild (>=0.7.1)", "Sphinx (>=2.2.1)", "pytest-cov (>=2.7.1)", "pytest (>=4.6.2)", "tox-travis (>=0.12)", "tox (>=3.9.0)", "flake8 (>=3.7.7)", "colorama (>=0.3.4)", "codecov (>=2.0.15)"]
+dev = ["colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "Sphinx (==7.2.5)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
[[package]]
-name = "makefun"
-version = "1.12.1"
description = "Small library to dynamically create python functions."
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "mo-dots"
-version = "4.22.21108"
-description = "More Dots! Dot-access to Python dicts like Javascript"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-mo-future = "3.147.20327"
-mo-imports = "3.149.20327"
-
-[[package]]
-name = "mo-future"
-version = "3.147.20327"
-description = "More future! Make Python 2/3 compatibility a bit easier"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "mo-imports"
-version = "3.149.20327"
-description = "More Imports! - Delayed importing"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-mo-future = "3.147.20327"
-
-[[package]]
-name = "mo-kwargs"
-version = "4.22.21108"
-description = "More KWARGS! Let call parameters override kwargs"
-category = "main"
+files = [
+ {file = "makefun-1.15.2-py2.py3-none-any.whl", hash = "sha256:1c83abfaefb6c3c7c83ed4a993b4a310af80adf6db15625b184b1f0f7545a041"},
+ {file = "makefun-1.15.2.tar.gz", hash = "sha256:16f2a2b34d9ee0c2b578c960a1808c974e2822cf79f6e9b9c455aace10882d45"}
+]
+name = "makefun"
optional = false
python-versions = "*"
-
-[package.dependencies]
-mo-dots = "4.22.21108"
-mo-future = "3.147.20327"
+version = "1.15.2"
[[package]]
-name = "mo-logs"
-version = "4.23.21108"
-description = "More Logs! Structured Logging and Exception Handling"
-category = "main"
+description = "Non-blocking MongoDB driver for Tornado or asyncio"
+files = [
+ {file = "motor-3.4.0-py3-none-any.whl", hash = "sha256:4b1e1a0cc5116ff73be2c080a72da078f2bb719b53bc7a6bb9e9a2f7dcd421ed"},
+ {file = "motor-3.4.0.tar.gz", hash = "sha256:c89b4e4eb2e711345e91c7c9b122cb68cce0e5e869ed0387dd0acb10775e3131"}
+]
+name = "motor"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+version = "3.4.0"
[package.dependencies]
-mo-dots = "4.22.21108"
-mo-future = "3.147.20327"
-mo-imports = "3.149.20327"
-mo-kwargs = "4.22.21108"
+pymongo = ">=4.5,<5"
-[[package]]
-name = "mongoengine"
-version = "0.23.1"
-description = "MongoEngine is a Python Object-Document Mapper for working with MongoDB."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-pymongo = ">=3.4,<4.0"
+[package.extras]
+aws = ["pymongo[aws] (>=4.5,<5)"]
+encryption = ["pymongo[encryption] (>=4.5,<5)"]
+gssapi = ["pymongo[gssapi] (>=4.5,<5)"]
+ocsp = ["pymongo[ocsp] (>=4.5,<5)"]
+snappy = ["pymongo[snappy] (>=4.5,<5)"]
+srv = ["pymongo[srv] (>=4.5,<5)"]
+test = ["aiohttp (!=3.8.6)", "mockupdb", "motor[encryption]", "pytest (>=7)", "tornado (>=5)"]
+zstd = ["pymongo[zstd] (>=4.5,<5)"]
[[package]]
-name = "moz-sql-parser"
-version = "4.40.21126"
-description = "Extract Parse Tree from SQL"
-category = "main"
+description = "multidict implementation"
+files = [
+ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"},
+ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"},
+ {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"},
+ {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"},
+ {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"},
+ {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"},
+ {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
+ {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
+ {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
+ {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"},
+ {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"},
+ {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"},
+ {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"},
+ {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"},
+ {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"},
+ {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"},
+ {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
+ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}
+]
+name = "multidict"
optional = false
-python-versions = "*"
-
-[package.dependencies]
-mo-dots = "4.22.21108"
-mo-future = "3.147.20327"
-mo-logs = "4.23.21108"
+python-versions = ">=3.7"
+version = "6.0.5"
[[package]]
-name = "multidict"
-version = "5.2.0"
-description = "multidict implementation"
-category = "main"
+description = "Type system extensions for programs checked with the mypy type checker."
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}
+]
+name = "mypy-extensions"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.5"
+version = "1.0.0"
[[package]]
-name = "networkx"
-version = "2.6.3"
description = "Python package for creating and manipulating graphs and networks"
-category = "dev"
+files = [
+ {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"},
+ {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}
+]
+name = "networkx"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.10"
+version = "3.3"
[package.extras]
-default = ["numpy (>=1.19)", "scipy (>=1.5,!=1.6.1)", "matplotlib (>=3.3)", "pandas (>=1.1)"]
-developer = ["black (==21.5b1)", "pre-commit (>=2.12)"]
-doc = ["sphinx (>=4.0,<5.0)", "pydata-sphinx-theme (>=0.6,<1.0)", "sphinx-gallery (>=0.9,<1.0)", "numpydoc (>=1.1)", "pillow (>=8.2)", "nb2plots (>=0.6)", "texext (>=0.6.6)"]
-extra = ["lxml (>=4.5)", "pygraphviz (>=1.7)", "pydot (>=1.4.1)"]
-test = ["pytest (>=6.2)", "pytest-cov (>=2.12)", "codecov (>=2.1)"]
+default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"]
+developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"]
+doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"]
+extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"]
+test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"]
[[package]]
-name = "nodeenv"
-version = "1.6.0"
description = "Node.js virtual environment builder"
-category = "dev"
+files = [
+ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
+ {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}
+]
+name = "nodeenv"
optional = false
-python-versions = "*"
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+version = "1.8.0"
-[[package]]
+[package.dependencies]
+setuptools = "*"
+
+[[package]]
+description = "Fundamental package for array computing in Python"
+files = [
+ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
+ {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
+ {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
+ {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
+ {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
+ {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
+ {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
+ {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
+ {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
+ {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
+ {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
+ {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
+ {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
+ {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
+ {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
+ {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
+ {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
+ {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
+ {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
+ {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
+ {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
+ {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
+ {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
+ {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
+ {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
+ {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
+ {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
+ {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
+ {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
+ {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
+ {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
+ {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
+ {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
+ {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
+ {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
+ {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}
+]
name = "numpy"
-version = "1.21.4"
-description = "NumPy is the fundamental package for array computing with Python."
-category = "main"
optional = false
-python-versions = ">=3.7,<3.11"
+python-versions = ">=3.9"
+version = "1.26.4"
[[package]]
-name = "packaging"
-version = "21.3"
description = "Core utilities for Python packages"
-category = "main"
+files = [
+ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
+ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}
+]
+name = "packaging"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+python-versions = ">=3.7"
+version = "24.0"
[[package]]
-name = "pandas"
-version = "1.1.5"
-description = "Powerful data structures for data analysis, time series, and statistics"
-category = "main"
+description = "Utility library for gitignore style pattern matching of file paths."
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}
+]
+name = "pathspec"
optional = false
-python-versions = ">=3.6.1"
-
-[package.dependencies]
-numpy = ">=1.15.4"
-python-dateutil = ">=2.7.3"
-pytz = ">=2017.2"
-
-[package.extras]
-test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"]
+python-versions = ">=3.8"
+version = "0.12.1"
[[package]]
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+files = [
+ {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
+ {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}
+]
name = "platformdirs"
-version = "2.4.0"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "4.2.1"
[package.extras]
-docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
+type = ["mypy (>=1.8)"]
[[package]]
-name = "pluggy"
-version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
-category = "dev"
+files = [
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}
+]
+name = "pluggy"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+python-versions = ">=3.8"
+version = "1.5.0"
[package.extras]
-testing = ["pytest-benchmark", "pytest"]
-dev = ["tox", "pre-commit"]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
[[package]]
-name = "pre-commit"
-version = "2.16.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "dev"
+files = [
+ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"},
+ {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}
+]
+name = "pre-commit"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.7"
+version = "2.21.0"
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
-toml = "*"
-virtualenv = ">=20.0.8"
+virtualenv = ">=20.10.0"
[[package]]
-name = "protobuf"
-version = "3.19.1"
description = "Protocol Buffers"
-category = "main"
+files = [
+ {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"},
+ {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"},
+ {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"},
+ {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"},
+ {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"},
+ {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"},
+ {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"},
+ {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"},
+ {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"},
+ {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"},
+ {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"},
+ {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"},
+ {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"},
+ {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"},
+ {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"},
+ {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"},
+ {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"},
+ {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"},
+ {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"},
+ {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"},
+ {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"},
+ {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"}
+]
+name = "protobuf"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
+version = "3.20.3"
[[package]]
-name = "psutil"
-version = "5.8.0"
description = "Cross-platform lib for process and system monitoring in Python."
-category = "main"
+files = [
+ {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
+ {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
+ {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
+ {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
+ {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
+ {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
+ {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
+ {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
+ {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
+ {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
+ {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
+ {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
+ {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
+ {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
+ {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
+ {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}
+]
+name = "psutil"
optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+version = "5.9.8"
[package.extras]
-test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
+test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
[[package]]
-name = "psycopg2-binary"
-version = "2.9.2"
-description = "psycopg2 - Python-PostgreSQL Database Adapter"
-category = "main"
+description = "Modern password hashing for Python"
+files = [
+ {file = "pwdlib-0.2.0-py3-none-any.whl", hash = "sha256:be53812012ab66795a57ac9393a59716ae7c2b60841ed453eb1262017fdec144"},
+ {file = "pwdlib-0.2.0.tar.gz", hash = "sha256:b1bdafc064310eb6d3d07144a210267063ab4f45ac73a97be948e6589f74e861"}
+]
+name = "pwdlib"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "0.2.0"
+
+[package.dependencies]
+argon2-cffi = {markers = "extra == \"argon2\"", optional = true, version = "23.1.0"}
+bcrypt = {markers = "extra == \"bcrypt\"", optional = true, version = "4.1.2"}
+
+[package.extras]
+argon2 = ["argon2-cffi (==23.1.0)"]
+bcrypt = ["bcrypt (==4.1.2)"]
[[package]]
-name = "py"
-version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "dev"
+files = [
+ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}
+]
+name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "1.11.0"
[[package]]
-name = "pycparser"
-version = "2.21"
description = "C parser in Python"
-category = "main"
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}
+]
+name = "pycparser"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.8"
+version = "2.22"
[[package]]
+description = "Data validation using Python type hints"
+files = [
+ {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
+ {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}
+]
name = "pydantic"
-version = "1.8.2"
-description = "Data validation and settings management using python 3.6 type hinting"
-category = "main"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.8"
+version = "2.7.1"
[package.dependencies]
-python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
-typing-extensions = ">=3.7.4.3"
+annotated-types = ">=0.4.0"
+pydantic-core = "2.18.2"
+typing-extensions = ">=4.6.1"
[package.extras]
-dotenv = ["python-dotenv (>=0.10.4)"]
-email = ["email-validator (>=1.0.3)"]
+email = ["email-validator (>=2.0.0)"]
+
+[[package]]
+description = "Core functionality for Pydantic validation and serialization"
+files = [
+ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
+ {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
+ {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
+ {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
+ {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
+ {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
+ {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
+ {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
+ {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
+ {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
+ {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
+ {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
+ {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
+ {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}
+]
+name = "pydantic-core"
+optional = false
+python-versions = ">=3.8"
+version = "2.18.2"
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
-name = "pyjwt"
-version = "2.4.0"
-description = "JSON Web Token implementation in Python"
-category = "main"
+description = "Settings management using Pydantic"
+files = [
+ {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"},
+ {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}
+]
+name = "pydantic-settings"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
+version = "2.2.1"
+
+[package.dependencies]
+pydantic = ">=2.3.0"
+python-dotenv = ">=0.21.0"
[package.extras]
-crypto = ["cryptography (>=3.3.1)"]
-dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
-docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
-tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
+toml = ["tomli (>=2.0.1)"]
+yaml = ["pyyaml (>=6.0.1)"]
[[package]]
-name = "pymongo"
-version = "3.12.2"
-description = "Python driver for MongoDB "
-category = "main"
+description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node"
+files = [
+ {file = "pyhumps-3.8.0-py3-none-any.whl", hash = "sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6"},
+ {file = "pyhumps-3.8.0.tar.gz", hash = "sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3"}
+]
+name = "pyhumps"
optional = false
python-versions = "*"
+version = "3.8.0"
+
+[[package]]
+description = "JSON Web Token implementation in Python"
+files = [
+ {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"},
+ {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}
+]
+name = "pyjwt"
+optional = false
+python-versions = ">=3.7"
+version = "2.8.0"
+
+[package.dependencies]
+cryptography = {markers = "extra == \"crypto\"", optional = true, version = ">=3.4.0"}
[package.extras]
-aws = ["pymongo-auth-aws (<2.0.0)"]
-encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"]
-gssapi = ["pykerberos"]
-ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"]
-snappy = ["python-snappy"]
-srv = ["dnspython (>=1.16.0,<1.17.0)"]
-tls = ["ipaddress"]
-zstd = ["zstandard"]
+crypto = ["cryptography (>=3.4.0)"]
+dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
-name = "pyparsing"
-version = "2.3.1"
-description = "Python parsing module"
-category = "main"
+description = "Python driver for MongoDB "
+files = [
+ {file = "pymongo-4.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:268d8578c0500012140c5460755ea405cbfe541ef47c81efa9d6744f0f99aeca"},
+ {file = "pymongo-4.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:827611beb6c483260d520cfa6a49662d980dfa5368a04296f65fa39e78fccea7"},
+ {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a754e366c404d19ff3f077ddeed64be31e0bb515e04f502bf11987f1baa55a16"},
+ {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44efab10d9a3db920530f7bcb26af8f408b7273d2f0214081d3891979726328"},
+ {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35b3f0c7d49724859d4df5f0445818d525824a6cd55074c42573d9b50764df67"},
+ {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e37faf298a37ffb3e0809e77fbbb0a32b6a2d18a83c59cfc2a7b794ea1136b0"},
+ {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1bcd58669e56c08f1e72c5758868b5df169fe267501c949ee83c418e9df9155"},
+ {file = "pymongo-4.7.2-cp310-cp310-win32.whl", hash = "sha256:c72d16fede22efe7cdd1f422e8da15760e9498024040429362886f946c10fe95"},
+ {file = "pymongo-4.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:12d1fef77d25640cb78893d07ff7d2fac4c4461d8eec45bd3b9ad491a1115d6e"},
+ {file = "pymongo-4.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc5af24fcf5fc6f7f40d65446400d45dd12bea933d0299dc9e90c5b22197f1e9"},
+ {file = "pymongo-4.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:730778b6f0964b164c187289f906bbc84cb0524df285b7a85aa355bbec43eb21"},
+ {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47a1a4832ef2f4346dcd1a10a36ade7367ad6905929ddb476459abb4fd1b98cb"},
+ {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6eab12c6385526d386543d6823b07187fefba028f0da216506e00f0e1855119"},
+ {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37e9ea81fa59ee9274457ed7d59b6c27f6f2a5fe8e26f184ecf58ea52a019cb8"},
+ {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9d9d2c0aae73aa4369bd373ac2ac59f02c46d4e56c4b6d6e250cfe85f76802"},
+ {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6e00a79dff22c9a72212ad82021b54bdb3b85f38a85f4fc466bde581d7d17a"},
+ {file = "pymongo-4.7.2-cp311-cp311-win32.whl", hash = "sha256:02efd1bb3397e24ef2af45923888b41a378ce00cb3a4259c5f4fc3c70497a22f"},
+ {file = "pymongo-4.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:87bb453ac3eb44db95cb6d5a616fbc906c1c00661eec7f55696253a6245beb8a"},
+ {file = "pymongo-4.7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:12c466e02133b7f8f4ff1045c6b5916215c5f7923bc83fd6e28e290cba18f9f6"},
+ {file = "pymongo-4.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f91073049c43d14e66696970dd708d319b86ee57ef9af359294eee072abaac79"},
+ {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87032f818bf5052ab742812c715eff896621385c43f8f97cdd37d15b5d394e95"},
+ {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a87eef394039765679f75c6a47455a4030870341cb76eafc349c5944408c882"},
+ {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d275596f840018858757561840767b39272ac96436fcb54f5cac6d245393fd97"},
+ {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82102e353be13f1a6769660dd88115b1da382447672ba1c2662a0fbe3df1d861"},
+ {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194065c9d445017b3c82fb85f89aa2055464a080bde604010dc8eb932a6b3c95"},
+ {file = "pymongo-4.7.2-cp312-cp312-win32.whl", hash = "sha256:db4380d1e69fdad1044a4b8f3bb105200542c49a0dde93452d938ff9db1d6d29"},
+ {file = "pymongo-4.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:fadc6e8db7707c861ebe25b13ad6aca19ea4d2c56bf04a26691f46c23dadf6e4"},
+ {file = "pymongo-4.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2cb77d09bd012cb4b30636e7e38d00b5f9be5eb521c364bde66490c45ee6c4b4"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bf8b706946952acdea0fe478f8e44f1ed101c4b87f046859e6c3abe6c0a9f4"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcf337d1b252405779d9c79978d6ca15eab3cdaa2f44c100a79221bddad97c8a"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ffd1519edbe311df73c74ec338de7d294af535b2748191c866ea3a7c484cd15"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d59776f435564159196d971aa89422ead878174aff8fe18e06d9a0bc6d648c"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:347c49cf7f0ba49ea87c1a5a1984187ecc5516b7c753f31938bf7b37462824fd"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84bc00200c3cbb6c98a2bb964c9e8284b641e4a33cf10c802390552575ee21de"},
+ {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fcaf8c911cb29316a02356f89dbc0e0dfcc6a712ace217b6b543805690d2aefd"},
+ {file = "pymongo-4.7.2-cp37-cp37m-win32.whl", hash = "sha256:b48a5650ee5320d59f6d570bd99a8d5c58ac6f297a4e9090535f6561469ac32e"},
+ {file = "pymongo-4.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5239ef7e749f1326ea7564428bf861d5250aa39d7f26d612741b1b1273227062"},
+ {file = "pymongo-4.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2dcf608d35644e8d276d61bf40a93339d8d66a0e5f3e3f75b2c155a421a1b71"},
+ {file = "pymongo-4.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25eeb2c18ede63891cbd617943dd9e6b9cbccc54f276e0b2e693a0cc40f243c5"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9349f0bb17a31371d4cacb64b306e4ca90413a3ad1fffe73ac7cd495570d94b5"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffd4d7cb2e6c6e100e2b39606d38a9ffc934e18593dc9bb326196afc7d93ce3d"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a8bd37f5dabc86efceb8d8cbff5969256523d42d08088f098753dba15f3b37a"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c78f156edc59b905c80c9003e022e1a764c54fd40ac4fea05b0764f829790e2"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d892fb91e81cccb83f507cdb2ea0aa026ec3ced7f12a1d60f6a5bf0f20f9c1f"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87832d6076c2c82f42870157414fd876facbb6554d2faf271ffe7f8f30ce7bed"},
+ {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ce1a374ea0e49808e0380ffc64284c0ce0f12bd21042b4bef1af3eb7bdf49054"},
+ {file = "pymongo-4.7.2-cp38-cp38-win32.whl", hash = "sha256:eb0642e5f0dd7e86bb358749cc278e70b911e617f519989d346f742dc9520dfb"},
+ {file = "pymongo-4.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:4bdb5ffe1cd3728c9479671a067ef44dacafc3743741d4dc700c377c4231356f"},
+ {file = "pymongo-4.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:743552033c63f0afdb56b9189ab04b5c1dbffd7310cf7156ab98eebcecf24621"},
+ {file = "pymongo-4.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5239776633f7578b81207e5646245415a5a95f6ae5ef5dff8e7c2357e6264bfc"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727ad07952c155cd20045f2ce91143c7dc4fb01a5b4e8012905a89a7da554b0c"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9385654f01a90f73827af4db90c290a1519f7d9102ba43286e187b373e9a78e9"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d833651f1ba938bb7501f13e326b96cfbb7d98867b2d545ca6d69c7664903e0"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf17ea9cea14d59b0527403dd7106362917ced7c4ec936c4ba22bd36c912c8e0"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cecd2df037249d1c74f0af86fb5b766104a5012becac6ff63d85d1de53ba8b98"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65b4c00dedbd333698b83cd2095a639a6f0d7c4e2a617988f6c65fb46711f028"},
+ {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9b6cbc037108ff1a0a867e7670d8513c37f9bcd9ee3d2464411bfabf70ca002"},
+ {file = "pymongo-4.7.2-cp39-cp39-win32.whl", hash = "sha256:cf28430ec1924af1bffed37b69a812339084697fd3f3e781074a0148e6475803"},
+ {file = "pymongo-4.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:e004527ea42a6b99a8b8d5b42b42762c3bdf80f88fbdb5c3a9d47f3808495b86"},
+ {file = "pymongo-4.7.2.tar.gz", hash = "sha256:9024e1661c6e40acf468177bf90ce924d1bc681d2b244adda3ed7b2f4c4d17d7"}
+]
+name = "pymongo"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+version = "4.7.2"
+
+[package.dependencies]
+dnspython = ">=1.16.0,<3.0.0"
+
+[package.extras]
+aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"]
+encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"]
+gssapi = ["pykerberos", "winkerberos (>=0.5.0)"]
+ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"]
+snappy = ["python-snappy"]
+test = ["pytest (>=7)"]
+zstd = ["zstandard"]
[[package]]
-name = "pytest"
-version = "6.2.5"
description = "pytest: simple powerful testing with Python"
-category = "dev"
+files = [
+ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
+ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}
+]
+name = "pytest"
optional = false
python-versions = ">=3.6"
+version = "6.2.5"
[package.dependencies]
-atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+atomicwrites = {markers = "sys_platform == \"win32\"", version = ">=1.0"}
attrs = ">=19.2.0"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+colorama = {markers = "sys_platform == \"win32\"", version = "*"}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
@@ -907,12 +1876,15 @@ toml = "*"
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
-name = "pytest-asyncio"
-version = "0.15.1"
description = "Pytest support for asyncio."
-category = "dev"
+files = [
+ {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"},
+ {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}
+]
+name = "pytest-asyncio"
optional = false
python-versions = ">= 3.6"
+version = "0.15.1"
[package.dependencies]
pytest = ">=5.4.0"
@@ -921,12 +1893,15 @@ pytest = ">=5.4.0"
testing = ["coverage", "hypothesis (>=5.7.1)"]
[[package]]
-name = "pytest-depends"
-version = "1.0.1"
description = "Tests that depend on other tests"
-category = "dev"
+files = [
+ {file = "pytest-depends-1.0.1.tar.gz", hash = "sha256:90a28e2b87b75b18abd128c94015248544acac20e4392e9921e5a86f93319dfe"},
+ {file = "pytest_depends-1.0.1-py3-none-any.whl", hash = "sha256:a1df072bcc93d77aca3f0946903f5fed8af2d9b0056db1dfc9ed5ac164ab0642"}
+]
+name = "pytest-depends"
optional = false
python-versions = "*"
+version = "1.0.1"
[package.dependencies]
colorama = "*"
@@ -935,644 +1910,752 @@ networkx = "*"
pytest = ">=3"
[[package]]
-name = "pytest-env"
-version = "0.6.2"
description = "py.test plugin that allows you to add environment variables."
-category = "dev"
+files = [
+ {file = "pytest-env-0.6.2.tar.gz", hash = "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2"}
+]
+name = "pytest-env"
optional = false
python-versions = "*"
+version = "0.6.2"
[package.dependencies]
pytest = ">=2.6.0"
[[package]]
-name = "pytest-ordering"
-version = "0.6"
description = "pytest plugin to run your tests in a specific order"
-category = "dev"
+files = [
+ {file = "pytest-ordering-0.6.tar.gz", hash = "sha256:561ad653626bb171da78e682f6d39ac33bb13b3e272d406cd555adb6b006bda6"},
+ {file = "pytest_ordering-0.6-py2-none-any.whl", hash = "sha256:27fba3fc265f5d0f8597e7557885662c1bdc1969497cd58aff6ed21c3b617de2"},
+ {file = "pytest_ordering-0.6-py3-none-any.whl", hash = "sha256:3f314a178dbeb6777509548727dc69edf22d6d9a2867bf2d310ab85c403380b6"}
+]
+name = "pytest-ordering"
optional = false
python-versions = "*"
+version = "0.6"
[package.dependencies]
pytest = "*"
[[package]]
-name = "python-dateutil"
-version = "2.8.2"
description = "Extensions to the standard Python datetime module"
-category = "main"
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}
+]
+name = "python-dateutil"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+version = "2.9.0.post0"
[package.dependencies]
six = ">=1.5"
[[package]]
-name = "python-dotenv"
-version = "0.19.2"
+description = "Strict separation of settings from code."
+files = [
+ {file = "python-decouple-3.8.tar.gz", hash = "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f"},
+ {file = "python_decouple-3.8-py3-none-any.whl", hash = "sha256:d0d45340815b25f4de59c974b855bb38d03151d81b037d9e3f463b0c9f8cbd66"}
+]
+name = "python-decouple"
+optional = false
+python-versions = "*"
+version = "3.8"
+
+[[package]]
description = "Read key-value pairs from a .env file and set them as environment variables"
-category = "main"
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}
+]
+name = "python-dotenv"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.8"
+version = "1.0.1"
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
-name = "python-multipart"
-version = "0.0.5"
description = "A streaming multipart parser for Python"
-category = "main"
+files = [
+ {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
+ {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}
+]
+name = "python-multipart"
optional = false
-python-versions = "*"
-
-[package.dependencies]
-six = ">=1.4.0"
+python-versions = ">=3.8"
+version = "0.0.9"
-[[package]]
-name = "pytz"
-version = "2021.3"
-description = "World timezone definitions, modern and historical"
-category = "main"
-optional = false
-python-versions = "*"
+[package.extras]
+dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"]
[[package]]
-name = "pyyaml"
-version = "5.4.1"
description = "YAML parser and emitter for Python"
-category = "main"
+files = [
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+ {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
+ {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
+ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}
+]
+name = "pyyaml"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.6"
+version = "6.0.1"
[[package]]
-name = "rdflib"
-version = "4.2.2"
-description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
-category = "main"
+description = "ReactiveX (Rx) for Python"
+files = [
+ {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"},
+ {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"}
+]
+name = "reactivex"
optional = false
-python-versions = "*"
+python-versions = ">=3.7,<4.0"
+version = "4.0.4"
[package.dependencies]
-isodate = "*"
-pyparsing = "*"
-
-[package.extras]
-html = ["html5lib"]
-sparql = ["sparqlwrapper"]
+typing-extensions = ">=4.1.1,<5.0.0"
[[package]]
-name = "requests"
-version = "2.26.0"
-description = "Python HTTP for Humans."
-category = "main"
+description = "Python client for Redis database and key-value store"
+files = [
+ {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"},
+ {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}
+]
+name = "redis"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.7"
+version = "5.0.4"
[package.dependencies]
-certifi = ">=2017.4.17"
-charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
-idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
-urllib3 = ">=1.21.1,<1.27"
+async-timeout = {markers = "python_full_version < \"3.11.3\"", version = ">=4.0.3"}
[package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
-use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+hiredis = ["hiredis (>=1.0.0)"]
+ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
[[package]]
-name = "rfc3986"
-version = "1.5.0"
-description = "Validating URI References per RFC 3986"
-category = "main"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+files = [
+ {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
+ {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}
+]
+name = "setuptools"
optional = false
-python-versions = "*"
-
-[package.dependencies]
-idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
+python-versions = ">=3.8"
+version = "69.5.1"
[package.extras]
-idna2008 = ["idna"]
-
-[[package]]
-name = "semver"
-version = "2.13.0"
-description = "Python helper for Semantic Versioning (http://semver.org/)"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[[package]]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
+[[package]]
+description = "Manipulation and analysis of geometric objects"
+files = [
+ {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f"},
+ {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17"},
+ {file = "shapely-2.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd"},
+ {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6"},
+ {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58"},
+ {file = "shapely-2.0.4-cp310-cp310-win32.whl", hash = "sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7"},
+ {file = "shapely-2.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9"},
+ {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35"},
+ {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7"},
+ {file = "shapely-2.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07"},
+ {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47"},
+ {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d"},
+ {file = "shapely-2.0.4-cp311-cp311-win32.whl", hash = "sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879"},
+ {file = "shapely-2.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d"},
+ {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328"},
+ {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65"},
+ {file = "shapely-2.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6"},
+ {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac"},
+ {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30"},
+ {file = "shapely-2.0.4-cp312-cp312-win32.whl", hash = "sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f"},
+ {file = "shapely-2.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0"},
+ {file = "shapely-2.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b"},
+ {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf"},
+ {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113"},
+ {file = "shapely-2.0.4-cp37-cp37m-win32.whl", hash = "sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53"},
+ {file = "shapely-2.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf"},
+ {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9"},
+ {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa"},
+ {file = "shapely-2.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e"},
+ {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679"},
+ {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"},
+ {file = "shapely-2.0.4-cp38-cp38-win32.whl", hash = "sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7"},
+ {file = "shapely-2.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c"},
+ {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0"},
+ {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa"},
+ {file = "shapely-2.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc"},
+ {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242"},
+ {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc"},
+ {file = "shapely-2.0.4-cp39-cp39-win32.whl", hash = "sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c"},
+ {file = "shapely-2.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398"},
+ {file = "shapely-2.0.4.tar.gz", hash = "sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8"}
+]
name = "shapely"
-version = "1.8.0"
-description = "Geometric objects, predicates, and operations"
-category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "2.0.4"
+
+[package.dependencies]
+numpy = ">=1.14,<3"
[package.extras]
-all = ["pytest", "pytest-cov", "numpy"]
+docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"]
test = ["pytest", "pytest-cov"]
-vectorized = ["numpy"]
[[package]]
-name = "six"
-version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
-category = "main"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}
+]
+name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "1.16.0"
[[package]]
-name = "sniffio"
-version = "1.2.0"
description = "Sniff out which async library your code is running under"
-category = "main"
+files = [
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}
+]
+name = "sniffio"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
+version = "1.3.1"
[[package]]
-name = "sparqlwrapper"
-version = "1.8.5"
-description = "SPARQL Endpoint interface to Python"
-category = "main"
+description = "The little ASGI library that shines."
+files = [
+ {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
+ {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}
+]
+name = "starlette"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
+version = "0.37.2"
[package.dependencies]
-rdflib = ">=4.0"
+anyio = ">=3.4.0,<5"
[package.extras]
-keepalive = ["keepalive (>=0.5)"]
+full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
[[package]]
-name = "sqlalchemy"
-version = "1.4.27"
-description = "Database Abstraction Library"
-category = "main"
+description = "Retry code until it succeeds"
+files = [
+ {file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"},
+ {file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"}
+]
+name = "tenacity"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
-
-[package.dependencies]
-greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+python-versions = ">=3.8"
+version = "8.3.0"
[package.extras]
-aiomysql = ["greenlet (!=0.4.17)", "aiomysql"]
-aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"]
-asyncio = ["greenlet (!=0.4.17)"]
-asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3)"]
-mariadb_connector = ["mariadb (>=1.0.1)"]
-mssql = ["pyodbc"]
-mssql_pymssql = ["pymssql"]
-mssql_pyodbc = ["pyodbc"]
-mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"]
-mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"]
-mysql_connector = ["mysql-connector-python"]
-oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"]
-postgresql = ["psycopg2 (>=2.7)"]
-postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"]
-postgresql_pg8000 = ["pg8000 (>=1.16.6)"]
-postgresql_psycopg2binary = ["psycopg2-binary"]
-postgresql_psycopg2cffi = ["psycopg2cffi"]
-pymysql = ["pymysql (<1)", "pymysql"]
-sqlcipher = ["sqlcipher3-binary"]
+doc = ["reno", "sphinx"]
+test = ["pytest", "tornado (>=4.5)", "typeguard"]
[[package]]
-name = "starlette"
-version = "0.19.1"
-description = "The little ASGI library that shines."
-category = "main"
+description = "Python Library for Tom's Obvious, Minimal Language"
+files = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}
+]
+name = "toml"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-anyio = ">=3.4.0,<5"
-typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "0.10.2"
[[package]]
-name = "tenacity"
-version = "8.1.0"
-description = "Retry code until it succeeds"
-category = "main"
+description = "A lil' TOML parser"
+files = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}
+]
+name = "tomli"
optional = false
-python-versions = ">=3.6"
-
-[package.extras]
-doc = ["reno", "sphinx", "tornado (>=4.5)"]
+python-versions = ">=3.7"
+version = "2.0.1"
[[package]]
-name = "timeout-decorator"
-version = "0.5.0"
-description = "Timeout decorator"
-category = "main"
+description = "Typing stubs for python-dateutil"
+files = [
+ {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"},
+ {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}
+]
+name = "types-python-dateutil"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
+version = "2.9.0.20240316"
[[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
-category = "dev"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+files = [
+ {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
+ {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}
+]
+name = "typing-extensions"
optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+python-versions = ">=3.8"
+version = "4.11.0"
[[package]]
-name = "typing-extensions"
-version = "4.0.1"
-description = "Backported and Experimental Type Hints for Python 3.6+"
-category = "main"
+description = "Runtime inspection utilities for typing module."
+files = [
+ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
+ {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}
+]
+name = "typing-inspect"
optional = false
-python-versions = ">=3.6"
+python-versions = "*"
+version = "0.9.0"
+
+[package.dependencies]
+mypy-extensions = ">=0.3.0"
+typing-extensions = ">=3.7.4"
[[package]]
-name = "urllib3"
-version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
+files = [
+ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
+ {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}
+]
+name = "urllib3"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+python-versions = ">=3.8"
+version = "2.2.1"
[package.extras]
-brotli = ["brotlipy (>=0.6.0)"]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
[[package]]
-name = "uvicorn"
-version = "0.15.0"
description = "The lightning-fast ASGI server."
-category = "main"
+files = [
+ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
+ {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}
+]
+name = "uvicorn"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
+version = "0.29.0"
[package.dependencies]
-asgiref = ">=3.4.0"
click = ">=7.0"
+colorama = {markers = "sys_platform == \"win32\" and extra == \"standard\"", optional = true, version = ">=0.4"}
h11 = ">=0.8"
-typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
+httptools = {markers = "extra == \"standard\"", optional = true, version = ">=0.5.0"}
+python-dotenv = {markers = "extra == \"standard\"", optional = true, version = ">=0.13"}
+pyyaml = {markers = "extra == \"standard\"", optional = true, version = ">=5.1"}
+typing-extensions = {markers = "python_version < \"3.11\"", version = ">=4.0"}
+uvloop = {markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\"", optional = true, version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1"}
+watchfiles = {markers = "extra == \"standard\"", optional = true, version = ">=0.13"}
+websockets = {markers = "extra == \"standard\"", optional = true, version = ">=10.4"}
[package.extras]
-standard = ["websockets (>=9.1)", "httptools (>=0.2.0,<0.3.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
-
-[[package]]
-name = "validators"
-version = "0.18.2"
-description = "Python Data Validation for Humans™."
-category = "main"
+standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[[package]]
+description = "Fast implementation of asyncio event loop on top of libuv"
+files = [
+ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"},
+ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"},
+ {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"},
+ {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"},
+ {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"},
+ {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"},
+ {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"},
+ {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"},
+ {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"},
+ {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"},
+ {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"},
+ {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"},
+ {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"},
+ {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"},
+ {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"},
+ {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"},
+ {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"},
+ {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"},
+ {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"},
+ {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"},
+ {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"},
+ {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"},
+ {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"},
+ {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"},
+ {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"},
+ {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"},
+ {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"},
+ {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"},
+ {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"},
+ {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"},
+ {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}
+]
+name = "uvloop"
optional = false
-python-versions = ">=3.4"
-
-[package.dependencies]
-decorator = ">=3.4.0"
-six = ">=1.4.0"
+python-versions = ">=3.8.0"
+version = "0.19.0"
[package.extras]
-test = ["pytest (>=2.2.3)", "flake8 (>=2.4.0)", "isort (>=4.2.2)"]
+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 = ["aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "Cython (>=0.29.36,<0.30.0)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pycodestyle (>=2.9.0,<2.10.0)", "pyOpenSSL (>=23.0.0,<23.1.0)"]
[[package]]
-name = "virtualenv"
-version = "20.10.0"
description = "Virtual Python Environment builder"
-category = "dev"
+files = [
+ {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"},
+ {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}
+]
+name = "virtualenv"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.7"
+version = "20.26.1"
[package.dependencies]
-"backports.entry-points-selectable" = ">=1.0.4"
-distlib = ">=0.3.1,<1"
-filelock = ">=3.2,<4"
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
-platformdirs = ">=2,<3"
-six = ">=1.9.0,<2"
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
[package.extras]
-docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
-testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+
+[[package]]
+description = "Simple, modern and high performance file watching and code reload in python."
+files = [
+ {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"},
+ {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"},
+ {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"},
+ {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"},
+ {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"},
+ {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"},
+ {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"},
+ {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"},
+ {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"},
+ {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"},
+ {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"},
+ {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"},
+ {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"},
+ {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"},
+ {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"},
+ {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"},
+ {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"},
+ {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"},
+ {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"},
+ {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"},
+ {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"},
+ {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"},
+ {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"},
+ {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"},
+ {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"},
+ {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"},
+ {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"},
+ {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"},
+ {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"},
+ {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"},
+ {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"},
+ {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"},
+ {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}
+]
+name = "watchfiles"
+optional = false
+python-versions = ">=3.8"
+version = "0.21.0"
+
+[package.dependencies]
+anyio = ">=3.0.0"
+
+[[package]]
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+files = [
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
+ {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"},
+ {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"},
+ {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"},
+ {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"},
+ {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"},
+ {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"},
+ {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"},
+ {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"},
+ {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"},
+ {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"},
+ {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"},
+ {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"},
+ {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"},
+ {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"},
+ {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"},
+ {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
+ {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}
+]
+name = "websockets"
+optional = false
+python-versions = ">=3.8"
+version = "12.0"
[[package]]
-name = "win32-setctime"
-version = "1.0.4"
description = "A small Python utility to set file creation time on Windows"
-category = "main"
+files = [
+ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
+ {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}
+]
+name = "win32-setctime"
optional = false
python-versions = ">=3.5"
+version = "1.1.0"
[package.extras]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
-name = "yarl"
-version = "1.7.2"
description = "Yet another URL library"
-category = "main"
+files = [
+ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
+ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
+ {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
+ {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
+ {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
+ {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
+ {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
+ {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
+ {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
+ {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
+ {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
+ {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
+ {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
+ {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
+ {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
+ {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
+ {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
+ {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
+ {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
+ {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
+ {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
+ {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"},
+ {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"},
+ {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"},
+ {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"},
+ {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"},
+ {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"},
+ {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"},
+ {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"},
+ {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"},
+ {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"},
+ {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"},
+ {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"},
+ {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"},
+ {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"},
+ {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"},
+ {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"},
+ {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"},
+ {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"},
+ {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"},
+ {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
+ {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}
+]
+name = "yarl"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+version = "1.9.4"
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
-typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
-
-[[package]]
-name = "zipp"
-version = "3.6.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
-
-[metadata]
-lock-version = "1.1"
-python-versions = ">=3.7,<3.11"
-content-hash = "eff81927ccb8f07b0f911ee83455362620f3030784da66064aecc20ced9a4a0d"
-
-[metadata.files]
-aiofiles = []
-aiohttp = [
- {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
- {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
- {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"},
- {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"},
- {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"},
- {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"},
- {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"},
- {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"},
- {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"},
- {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"},
- {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"},
- {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"},
- {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"},
- {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"},
- {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"},
- {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"},
- {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"},
- {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"},
- {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"},
- {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"},
- {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"},
- {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"},
- {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"},
- {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"},
- {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"},
- {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"},
- {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"},
- {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"},
- {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"},
- {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"},
- {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"},
- {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"},
- {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"},
- {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"},
- {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"},
- {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"},
- {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"},
- {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"},
- {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"},
- {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"},
- {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"},
- {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"},
- {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"},
- {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"},
- {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"},
- {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"},
- {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"},
- {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"},
- {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"},
- {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"},
- {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"},
- {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"},
- {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"},
- {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"},
- {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"},
- {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"},
- {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"},
- {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"},
- {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"},
- {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"},
- {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"},
- {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"},
- {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"},
- {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"},
- {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"},
- {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"},
- {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"},
- {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"},
- {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"},
- {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"},
- {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"},
- {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"},
-]
-aiosignal = [
- {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
- {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
-]
-aiosparql = []
-anyio = []
-arrow = []
-asgi-lifespan = []
-asgiref = []
-async-timeout = []
-asyncpg = []
-asynctest = []
-atomicwrites = []
-attrs = []
-authlib = []
-"backports.entry-points-selectable" = []
-brick-data = []
-certifi = []
-cffi = []
-cfgv = [
- {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
- {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
-]
-charset-normalizer = []
-click = []
-click-default-group = []
-click-option-group = []
-colorama = []
-cryptography = []
-decorator = []
-distlib = []
-fastapi = [
- {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
- {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
-]
-fastapi-rest-framework = []
-fastapi-utils = []
-filelock = []
-frozenlist = []
-future-fstrings = []
-geoalchemy2 = []
-greenlet = []
-grpcio = []
-h11 = [
- {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
- {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
-]
-httpcore = []
-httpx = []
-identify = []
-idna = []
-importlib-metadata = []
-iniconfig = []
-isodate = []
-itsdangerous = []
-loguru = []
-makefun = []
-mo-dots = []
-mo-future = []
-mo-imports = []
-mo-kwargs = []
-mo-logs = []
-mongoengine = []
-moz-sql-parser = []
-multidict = []
-networkx = []
-nodeenv = []
-numpy = []
-packaging = []
-pandas = []
-platformdirs = []
-pluggy = []
-pre-commit = []
-protobuf = []
-psutil = []
-psycopg2-binary = []
-py = []
-pycparser = []
-pydantic = []
-pyjwt = []
-pymongo = []
-pyparsing = []
-pytest = []
-pytest-asyncio = []
-pytest-depends = []
-pytest-env = []
-pytest-ordering = []
-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 = []
-python-multipart = []
-pytz = []
-pyyaml = []
-rdflib = []
-requests = []
-rfc3986 = [
- {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
- {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
-]
-semver = []
-shapely = []
-six = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-sniffio = [
- {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
- {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
-]
-sparqlwrapper = []
-sqlalchemy = []
-starlette = [
- {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
- {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
-]
-tenacity = []
-timeout-decorator = []
-toml = [
- {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
- {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
-]
-typing-extensions = []
-urllib3 = []
-uvicorn = [
- {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"},
- {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"},
-]
-validators = []
-virtualenv = []
-win32-setctime = []
-yarl = [
- {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},
- {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"},
- {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"},
- {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"},
- {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"},
- {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"},
- {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"},
- {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"},
- {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"},
- {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"},
- {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"},
- {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"},
- {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"},
- {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"},
- {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"},
- {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"},
- {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"},
- {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"},
- {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"},
- {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"},
- {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"},
- {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"},
- {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"},
- {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"},
- {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"},
- {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"},
- {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"},
- {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"},
- {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"},
- {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"},
- {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"},
- {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"},
- {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"},
- {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"},
- {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"},
- {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"},
- {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"},
- {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"},
- {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"},
- {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"},
- {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"},
- {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"},
- {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"},
- {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"},
- {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"},
- {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"},
- {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"},
- {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"},
- {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"},
- {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"},
- {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"},
- {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"},
- {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"},
- {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"},
- {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"},
- {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"},
- {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"},
- {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"},
- {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"},
- {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"},
- {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"},
- {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"},
- {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"},
- {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"},
- {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"},
- {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"},
- {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"},
- {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"},
- {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"},
- {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"},
- {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"},
- {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"},
-]
-zipp = []
diff --git a/pyproject.toml b/pyproject.toml
index d08a92c..bc0d5c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,48 +10,45 @@ description = "brick server minimal"
license = "BSD-3-Clause"
name = "brick-server-minimal"
packages = [
- { include = "brick_server" },
+ {include = "brick_server"}
]
repository = "https://gitlab.com/mesl/brickserver/brick-server-minimal"
version = "0.1.0"
[tool.poetry.dependencies]
+aiocache = {extras = ["redis"], version = "^0.12.1"}
aiofiles = "^0.7.0"
-aiosparql = "^0.12.0"
-arrow = "^1.1.1"
-asyncpg = "^0.24.0"
-Authlib = "^0.15.4"
-brick-data = {git = "https://github.com/jbkoh/brick_data.git", rev = "master"}
+arrow = "^1.3.0"
+asyncpg = "^0.29.0"
click-default-group = "^1.2.2"
-fastapi = "^0.78.0"
-fastapi-rest-framework = {git = "https://github.com/joint-online-judge/fastapi-rest-framework", rev = "master"}
-fastapi-utils = "^0.2.1"
-grpcio = "^1.42.0"
-httpx = "^0.23.0"
-itsdangerous = "^2.0.1"
-mongoengine = "^0.23.1"
+fastapi = "^0.110.1"
+fastapi-restful = "^0.5.0"
+fastapi-users = {extras = ["beanie", "oauth"], version = "^13.0.0"}
+grpcio = "^1.63.0"
+httpx = "^0.27.0"
+influxdb-client = {extras = ["async"], version = "^1.37.0"}
+loguru = "^0.7.2"
protobuf = "^3.17.3"
-psycopg2-binary = "^2.9.1"
-pydantic = "^1.8.2"
-PyJWT = "^2.1.0"
-pyparsing = "2.3.1"
-python = ">=3.7,<3.11"
-python-multipart = "^0.0.5"
-PyYAML = "^5.4.1"
-rdflib = "^4.2.2"
-requests = "^2.26.0"
-tenacity = "^8.1.0"
-timeout-decorator = "^0.5.0"
-uvicorn = "^0.15.0"
+pydantic = "^2.7.0"
+pydantic-settings = "^2.2.1"
+pyhumps = "^3.8.0"
+python = "^3.10"
+python-decouple = "^3.8"
+python-multipart = "^0.0.9"
+shapely = "^2.0.3"
+typing-inspect = "^0.9.0"
+uvicorn = {extras = ["standard"], version = "^0.29.0"}
[tool.poetry.dev-dependencies]
asgi-lifespan = "^1.0.1"
+black = "^24.4.0"
pre-commit = "^2.15.0"
pytest = "^6.2.5"
pytest-asyncio = "^0.15.1"
pytest-depends = "^1.0.1"
pytest-env = "^0.6.2"
pytest-ordering = "^0.6"
+tenacity = "^8.1.0"
[tool.pycln]
all = true
diff --git a/setup.cfg b/setup.cfg
index ec105f2..c3c2926 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
[flake8]
per-file-ignores = __init__.py:F401
max-line-length = 131
-ignore = E203,E402,W503,F841,E501,E741
+ignore = E203,E402,W503,F841,E501,E741,F541
[mypy]
plugins = pydantic.mypy
diff --git a/tests/conftest.py b/tests/conftest.py
index 5067a73..0044dad 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,11 +4,11 @@
import pytest
from asgi_lifespan import LifespanManager
from fastapi import FastAPI
-from fastapi_rest_framework.config import settings
from httpx import AsyncClient
from brick_server.minimal.app import app as fastapi_app
-from brick_server.minimal.auth.authorization import create_jwt_token
+from brick_server.minimal.auth.jwt import create_jwt_token
+from brick_server.minimal.config.manager import settings
from brick_server.minimal.interfaces.graphdb import GraphDB
from brick_server.minimal.models import User
from brick_server.minimal.schemas import Domain
@@ -31,7 +31,7 @@ def event_loop(request: Any) -> Generator[asyncio.AbstractEventLoop, Any, Any]:
@pytest.fixture(scope="session", autouse=True)
async def prepare_db(request: Any):
- settings.timescale_dbname += "-test"
+ settings.TIMESCALE_DATABASE += "-test"
settings.mongo_dbname += "-test"
await drop_postgres_db()
await create_postgres_db()
diff --git a/tests/utils.py b/tests/utils.py
index 4a44a41..8575a15 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,9 +1,9 @@
import arrow
import asyncpg
-from fastapi_rest_framework.config import settings
from mongoengine import connect as mongo_connect
from tenacity import retry, stop_after_delay, wait_exponential
+from brick_server.minimal.config.manager import settings
from brick_server.minimal.interfaces.graphdb import GraphDB
from brick_server.minimal.models import User
@@ -44,10 +44,10 @@ async def drop_postgres_db():
"drop test db",
settings.timescale_host,
settings.timescale_port,
- settings.timescale_dbname,
+ settings.TIMESCALE_DATABASE,
)
conn = await get_postgres_conn()
- await conn.execute(f'DROP DATABASE IF EXISTS "{settings.timescale_dbname}"')
+ await conn.execute(f'DROP DATABASE IF EXISTS "{settings.TIMESCALE_DATABASE}"')
await conn.close()
@@ -56,14 +56,14 @@ async def create_postgres_db():
"create test db",
settings.timescale_host,
settings.timescale_port,
- settings.timescale_dbname,
+ settings.TIMESCALE_DATABASE,
)
conn = await get_postgres_conn()
await conn.execute(
- f'CREATE DATABASE "{settings.timescale_dbname}" OWNER "{settings.timescale_username}"'
+ f'CREATE DATABASE "{settings.TIMESCALE_DATABASE}" OWNER "{settings.timescale_username}"'
)
await conn.close()
- conn = await get_postgres_conn(database=settings.timescale_dbname)
+ conn = await get_postgres_conn(database=settings.TIMESCALE_DATABASE)
await conn.execute("CREATE EXTENSION IF NOT EXISTS postgis CASCADE")
await conn.close()