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()