From 949dca253ce5c23b6e80e6f6c5b1480d46bac996 Mon Sep 17 00:00:00 2001 From: Mihai Criveti Date: Tue, 27 May 2025 00:27:14 +0100 Subject: [PATCH] Open Source release of MCP Context Forge - MCP Gateway --- .coveragerc | 14 + .darglint | 2 + .dockerignore | 174 + .env.ce.example | 45 + .env.example | 228 ++ .eslintrc.json | 16 + .flake8 | 2 + .gitignore | 197 ++ .hadolint.yaml | 2 + .htmlhintrc | 7 + .pre-commit-config.yaml | 218 ++ .pycodestyle | 5 + .pylintrc | 649 ++++ .pyspelling.yml | 28 + .ruff.toml | 63 + .spellcheck-en.txt | 440 +++ .stylelintrc.json | 6 + .travis.yml | 32 + .whitesource | 1 + CONTRIBUTING.md | 84 + Containerfile | 40 + Containerfile.lite | 136 + DEVELOPING.md | 24 + LICENSE | 201 ++ Makefile | 1063 ++++++ README.md | 971 ++++++ TESTING.md | 103 + docs/.gitignore | 7 + docs/Makefile | 94 + docs/base.yml | 163 + docs/create.sh | 89 + docs/docs/.pages | 7 + docs/docs/blog/index.md | 1 + docs/docs/deployment/.pages | 9 + docs/docs/deployment/aws.md | 82 + docs/docs/deployment/azure.md | 84 + docs/docs/deployment/container.md | 83 + docs/docs/deployment/ibm-code-engine.md | 453 +++ docs/docs/deployment/index.md | 53 + docs/docs/deployment/kubernetes.md | 126 + docs/docs/deployment/local.md | 67 + docs/docs/development/.pages | 5 + docs/docs/development/building.md | 79 + docs/docs/development/index.md | 47 + docs/docs/development/packaging.md | 83 + docs/docs/images/mcpgateway.gif | Bin 0 -> 1552478 bytes docs/docs/index.md | 61 + docs/docs/manage/.pages | 7 + docs/docs/manage/backup.md | 69 + docs/docs/manage/index.md | 53 + docs/docs/manage/logging.md | 75 + docs/docs/manage/tuning.md | 130 + docs/docs/manage/upgrade.md | 63 + docs/docs/overview/.pages | 5 + docs/docs/overview/features.md | 96 + docs/docs/overview/index.md | 27 + docs/docs/overview/ui.md | 58 + docs/docs/using/.pages | 6 + docs/docs/using/agents/.pages | 11 + docs/docs/using/agents/autogen.md | 18 + docs/docs/using/agents/bee.md | 110 + docs/docs/using/agents/crewai.md | 20 + docs/docs/using/agents/index.md | 36 + docs/docs/using/agents/langchain.md | 79 + docs/docs/using/agents/langgraph.md | 79 + docs/docs/using/agents/llamaindex.md | 11 + docs/docs/using/agents/openai-sdk.md | 15 + docs/docs/using/agents/semantic-kernel.md | 18 + docs/docs/using/clients/.pages | 8 + docs/docs/using/clients/claude-desktop.md | 72 + docs/docs/using/clients/cline.md | 86 + docs/docs/using/clients/continue.md | 91 + docs/docs/using/clients/index.md | 38 + docs/docs/using/clients/mcp-inspector.md | 64 + docs/docs/using/clients/openwebui.md | 98 + docs/docs/using/index.md | 36 + docs/docs/using/mcpgateway-wrapper.md | 146 + docs/mkdocs.yml | 101 + docs/requirements.txt | 90 + docs/theme/logo.png | Bin 0 -> 6853 bytes gunicorn.config.py | 80 + mcpgateway/__init__.py | 8 + mcpgateway/admin.py | 1259 +++++++ mcpgateway/cache/__init__.py | 15 + mcpgateway/cache/resource_cache.py | 136 + mcpgateway/cache/session_registry.py | 760 +++++ mcpgateway/config.py | 392 +++ mcpgateway/db.py | 1050 ++++++ mcpgateway/federation/__init__.py | 26 + mcpgateway/federation/discovery.py | 390 +++ mcpgateway/federation/forward.py | 337 ++ mcpgateway/federation/manager.py | 459 +++ mcpgateway/handlers/__init__.py | 14 + mcpgateway/handlers/sampling.py | 225 ++ mcpgateway/main.py | 2034 +++++++++++ mcpgateway/schemas.py | 1210 +++++++ mcpgateway/services/__init__.py | 29 + mcpgateway/services/completion_service.py | 200 ++ mcpgateway/services/gateway_service.py | 709 ++++ mcpgateway/services/logging_service.py | 162 + mcpgateway/services/prompt_service.py | 740 ++++ mcpgateway/services/resource_service.py | 875 +++++ mcpgateway/services/root_service.py | 176 + mcpgateway/services/server_service.py | 624 ++++ mcpgateway/services/tool_service.py | 838 +++++ mcpgateway/static/admin.css | 9 + mcpgateway/static/admin.js | 1676 ++++++++++ mcpgateway/templates/admin.html | 2465 ++++++++++++++ mcpgateway/transports/__init__.py | 19 + mcpgateway/transports/base.py | 48 + mcpgateway/transports/sse_transport.py | 230 ++ mcpgateway/transports/stdio_transport.py | 113 + mcpgateway/transports/websocket_transport.py | 139 + mcpgateway/types.py | 745 +++++ mcpgateway/utils/create_jwt_token.py | 234 ++ mcpgateway/utils/services_auth.py | 78 + mcpgateway/utils/verify_credentials.py | 182 + mcpgateway/validation/__init__.py | 18 + mcpgateway/validation/jsonrpc.py | 142 + os_deps.sh | 125 + package-lock.json | 1498 +++++++++ package.json | 5 + podman-compose-sonarqube.yaml | 39 + pyproject.toml | 193 ++ run-gunicorn.sh | 48 + run.sh | 195 ++ sonar-code.properties | 7 + test_readme.py | 38 + tests/__init__.py | 8 + tests/conftest.py | 95 + tests/e2e/__init__.py | 8 + tests/e2e/test_admin_workflow.py | 8 + tests/e2e/test_client_workflow.py | 8 + tests/e2e/test_federation_workflow.py | 8 + tests/hey/hey.sh | 154 + tests/hey/payload.json | 10 + tests/integration/__init__.py | 8 + tests/integration/test_api_endpoints.py | 8 + tests/integration/test_federation.py | 8 + tests/integration/test_prompt_rendering.py | 8 + tests/integration/test_resource_management.py | 8 + tests/integration/test_server_catalog.py | 8 + tests/integration/test_tool_invocation.py | 8 + tests/unit/__init__.py | 8 + tests/unit/mcpgateway/__init__.py | 8 + tests/unit/mcpgateway/admin/__init__.py | 8 + tests/unit/mcpgateway/admin/test_routes.py | 8 + tests/unit/mcpgateway/cache/__init__.py | 8 + .../mcpgateway/cache/test_resource_cache.py | 8 + tests/unit/mcpgateway/db/__init__.py | 8 + tests/unit/mcpgateway/db/test_init.py | 8 + tests/unit/mcpgateway/db/test_models.py | 8 + tests/unit/mcpgateway/federation/__init__.py | 8 + .../mcpgateway/federation/test_discovery.py | 8 + .../mcpgateway/federation/test_forward.py | 8 + .../mcpgateway/federation/test_manager.py | 8 + tests/unit/mcpgateway/handlers/__init__.py | 8 + .../unit/mcpgateway/handlers/test_sampling.py | 8 + tests/unit/mcpgateway/services/__init__.py | 8 + .../services/test_completion_service.py | 184 + .../services/test_gateway_service.py | 496 +++ .../services/test_logging_service.py | 158 + .../services/test_prompt_service.py | 602 ++++ .../services/test_resource_service.py | 235 ++ .../mcpgateway/services/test_root_service.py | 133 + .../services/test_server_service.py | 546 +++ .../mcpgateway/services/test_tool_service.py | 670 ++++ tests/unit/mcpgateway/test_admin.py | 706 ++++ tests/unit/mcpgateway/test_config.py | 70 + tests/unit/mcpgateway/test_main.py | 342 ++ tests/unit/mcpgateway/test_schemas.py | 829 +++++ tests/unit/mcpgateway/test_types.py | 570 ++++ tests/unit/mcpgateway/transports/__init__.py | 8 + tests/unit/mcpgateway/transports/test_base.py | 8 + .../transports/test_sse_transport.py | 157 + .../transports/test_stdio_transport.py | 274 ++ .../transports/test_websocket_transport.py | 175 + tests/unit/mcpgateway/types/__init__.py | 8 + tests/unit/mcpgateway/types/test_types.py | 8 + tests/unit/mcpgateway/validation/__init__.py | 8 + .../mcpgateway/validation/test_jsonrpc.py | 153 + uv.lock | 2974 +++++++++++++++++ 182 files changed, 38951 insertions(+) create mode 100644 .coveragerc create mode 100644 .darglint create mode 100644 .dockerignore create mode 100644 .env.ce.example create mode 100644 .env.example create mode 100644 .eslintrc.json create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .hadolint.yaml create mode 100644 .htmlhintrc create mode 100644 .pre-commit-config.yaml create mode 100644 .pycodestyle create mode 100644 .pylintrc create mode 100644 .pyspelling.yml create mode 100644 .ruff.toml create mode 100644 .spellcheck-en.txt create mode 100644 .stylelintrc.json create mode 100644 .travis.yml create mode 100644 .whitesource create mode 100644 CONTRIBUTING.md create mode 100644 Containerfile create mode 100644 Containerfile.lite create mode 100644 DEVELOPING.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 TESTING.md create mode 100644 docs/.gitignore create mode 100644 docs/Makefile create mode 100644 docs/base.yml create mode 100755 docs/create.sh create mode 100644 docs/docs/.pages create mode 100644 docs/docs/blog/index.md create mode 100644 docs/docs/deployment/.pages create mode 100644 docs/docs/deployment/aws.md create mode 100644 docs/docs/deployment/azure.md create mode 100644 docs/docs/deployment/container.md create mode 100644 docs/docs/deployment/ibm-code-engine.md create mode 100644 docs/docs/deployment/index.md create mode 100644 docs/docs/deployment/kubernetes.md create mode 100644 docs/docs/deployment/local.md create mode 100644 docs/docs/development/.pages create mode 100644 docs/docs/development/building.md create mode 100644 docs/docs/development/index.md create mode 100644 docs/docs/development/packaging.md create mode 100755 docs/docs/images/mcpgateway.gif create mode 100644 docs/docs/index.md create mode 100644 docs/docs/manage/.pages create mode 100644 docs/docs/manage/backup.md create mode 100644 docs/docs/manage/index.md create mode 100644 docs/docs/manage/logging.md create mode 100644 docs/docs/manage/tuning.md create mode 100644 docs/docs/manage/upgrade.md create mode 100644 docs/docs/overview/.pages create mode 100644 docs/docs/overview/features.md create mode 100644 docs/docs/overview/index.md create mode 100644 docs/docs/overview/ui.md create mode 100644 docs/docs/using/.pages create mode 100644 docs/docs/using/agents/.pages create mode 100644 docs/docs/using/agents/autogen.md create mode 100644 docs/docs/using/agents/bee.md create mode 100644 docs/docs/using/agents/crewai.md create mode 100644 docs/docs/using/agents/index.md create mode 100644 docs/docs/using/agents/langchain.md create mode 100644 docs/docs/using/agents/langgraph.md create mode 100644 docs/docs/using/agents/llamaindex.md create mode 100644 docs/docs/using/agents/openai-sdk.md create mode 100644 docs/docs/using/agents/semantic-kernel.md create mode 100644 docs/docs/using/clients/.pages create mode 100644 docs/docs/using/clients/claude-desktop.md create mode 100644 docs/docs/using/clients/cline.md create mode 100644 docs/docs/using/clients/continue.md create mode 100644 docs/docs/using/clients/index.md create mode 100644 docs/docs/using/clients/mcp-inspector.md create mode 100644 docs/docs/using/clients/openwebui.md create mode 100644 docs/docs/using/index.md create mode 100644 docs/docs/using/mcpgateway-wrapper.md create mode 100644 docs/mkdocs.yml create mode 100644 docs/requirements.txt create mode 100644 docs/theme/logo.png create mode 100644 gunicorn.config.py create mode 100644 mcpgateway/__init__.py create mode 100644 mcpgateway/admin.py create mode 100644 mcpgateway/cache/__init__.py create mode 100644 mcpgateway/cache/resource_cache.py create mode 100644 mcpgateway/cache/session_registry.py create mode 100644 mcpgateway/config.py create mode 100644 mcpgateway/db.py create mode 100644 mcpgateway/federation/__init__.py create mode 100644 mcpgateway/federation/discovery.py create mode 100644 mcpgateway/federation/forward.py create mode 100644 mcpgateway/federation/manager.py create mode 100644 mcpgateway/handlers/__init__.py create mode 100644 mcpgateway/handlers/sampling.py create mode 100644 mcpgateway/main.py create mode 100644 mcpgateway/schemas.py create mode 100644 mcpgateway/services/__init__.py create mode 100644 mcpgateway/services/completion_service.py create mode 100644 mcpgateway/services/gateway_service.py create mode 100644 mcpgateway/services/logging_service.py create mode 100644 mcpgateway/services/prompt_service.py create mode 100644 mcpgateway/services/resource_service.py create mode 100644 mcpgateway/services/root_service.py create mode 100644 mcpgateway/services/server_service.py create mode 100644 mcpgateway/services/tool_service.py create mode 100644 mcpgateway/static/admin.css create mode 100644 mcpgateway/static/admin.js create mode 100644 mcpgateway/templates/admin.html create mode 100644 mcpgateway/transports/__init__.py create mode 100644 mcpgateway/transports/base.py create mode 100644 mcpgateway/transports/sse_transport.py create mode 100644 mcpgateway/transports/stdio_transport.py create mode 100644 mcpgateway/transports/websocket_transport.py create mode 100644 mcpgateway/types.py create mode 100644 mcpgateway/utils/create_jwt_token.py create mode 100644 mcpgateway/utils/services_auth.py create mode 100644 mcpgateway/utils/verify_credentials.py create mode 100644 mcpgateway/validation/__init__.py create mode 100644 mcpgateway/validation/jsonrpc.py create mode 100755 os_deps.sh create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 podman-compose-sonarqube.yaml create mode 100644 pyproject.toml create mode 100755 run-gunicorn.sh create mode 100755 run.sh create mode 100644 sonar-code.properties create mode 100644 test_readme.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/e2e/__init__.py create mode 100644 tests/e2e/test_admin_workflow.py create mode 100644 tests/e2e/test_client_workflow.py create mode 100644 tests/e2e/test_federation_workflow.py create mode 100755 tests/hey/hey.sh create mode 100644 tests/hey/payload.json create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/test_api_endpoints.py create mode 100644 tests/integration/test_federation.py create mode 100644 tests/integration/test_prompt_rendering.py create mode 100644 tests/integration/test_resource_management.py create mode 100644 tests/integration/test_server_catalog.py create mode 100644 tests/integration/test_tool_invocation.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/mcpgateway/__init__.py create mode 100644 tests/unit/mcpgateway/admin/__init__.py create mode 100644 tests/unit/mcpgateway/admin/test_routes.py create mode 100644 tests/unit/mcpgateway/cache/__init__.py create mode 100644 tests/unit/mcpgateway/cache/test_resource_cache.py create mode 100644 tests/unit/mcpgateway/db/__init__.py create mode 100644 tests/unit/mcpgateway/db/test_init.py create mode 100644 tests/unit/mcpgateway/db/test_models.py create mode 100644 tests/unit/mcpgateway/federation/__init__.py create mode 100644 tests/unit/mcpgateway/federation/test_discovery.py create mode 100644 tests/unit/mcpgateway/federation/test_forward.py create mode 100644 tests/unit/mcpgateway/federation/test_manager.py create mode 100644 tests/unit/mcpgateway/handlers/__init__.py create mode 100644 tests/unit/mcpgateway/handlers/test_sampling.py create mode 100644 tests/unit/mcpgateway/services/__init__.py create mode 100644 tests/unit/mcpgateway/services/test_completion_service.py create mode 100644 tests/unit/mcpgateway/services/test_gateway_service.py create mode 100644 tests/unit/mcpgateway/services/test_logging_service.py create mode 100644 tests/unit/mcpgateway/services/test_prompt_service.py create mode 100644 tests/unit/mcpgateway/services/test_resource_service.py create mode 100644 tests/unit/mcpgateway/services/test_root_service.py create mode 100644 tests/unit/mcpgateway/services/test_server_service.py create mode 100644 tests/unit/mcpgateway/services/test_tool_service.py create mode 100644 tests/unit/mcpgateway/test_admin.py create mode 100644 tests/unit/mcpgateway/test_config.py create mode 100644 tests/unit/mcpgateway/test_main.py create mode 100644 tests/unit/mcpgateway/test_schemas.py create mode 100644 tests/unit/mcpgateway/test_types.py create mode 100644 tests/unit/mcpgateway/transports/__init__.py create mode 100644 tests/unit/mcpgateway/transports/test_base.py create mode 100644 tests/unit/mcpgateway/transports/test_sse_transport.py create mode 100644 tests/unit/mcpgateway/transports/test_stdio_transport.py create mode 100644 tests/unit/mcpgateway/transports/test_websocket_transport.py create mode 100644 tests/unit/mcpgateway/types/__init__.py create mode 100644 tests/unit/mcpgateway/types/test_types.py create mode 100644 tests/unit/mcpgateway/validation/__init__.py create mode 100644 tests/unit/mcpgateway/validation/test_jsonrpc.py create mode 100644 uv.lock diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..b6653290ef --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ +[run] +branch = True + +[report] +exclude_lines = + pragma: no cover + def __repr__ + if self.debug + raise AssertionError + raise NotImplentedError + if 0: + if __name__ == .__main__.: + +ignore_errors = True diff --git a/.darglint b/.darglint new file mode 100644 index 0000000000..68a1086042 --- /dev/null +++ b/.darglint @@ -0,0 +1,2 @@ +[darglint] +docstring_style=google diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..ff228d8368 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,174 @@ +node_modules +docs/ +.vscode +certs +mcp.db +public/ +# VENV +.python37/ +.python39/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Bak +*.bak + +# C extensions +*.so + +# Distribution / packaging +.wily/ +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Mac junk +.DS_Store + +# MYPY cache +.mypy_cache +.idea/ + +# Sonar +.scannerwork + +# vim +*.swp +*,cover + +# Development folders +.git/ +attic/ +docs/ +test/ +tests/ + +# Configuration files +.bumpversion.cfg +.coveragerc +.editorconfig +.flake8 +.hadolint.yaml +.pre-commit-config.yaml +.pycodestyle +.pylintrc +.pyre_configuration +.pyspelling.yaml +.ruff.toml +.shellcheckrc +.spellcheck-en.txt +.travis.* +.whitesource +Makefile +mypi.ini +pytest.ini +sonar-code.properties +test.sh +tox.ini +whitesource.config + +# Misc +TODO.md +DEVELOPING.md diff --git a/.env.ce.example b/.env.ce.example new file mode 100644 index 0000000000..6a3511f998 --- /dev/null +++ b/.env.ce.example @@ -0,0 +1,45 @@ +############################################################################### +# IBM Cloud Code Engine – deployment-only variables +# • Required *only* when you deploy MCP Gateway to IBM Cloud. +# • These keys are consumed by the Makefile / ibmcloud CLI and are **NOT** +# injected into the running container. +# • Copy this file to `.env.ce`, fill in real values, keep it out of Git. +############################################################################### + +# ── Core IBM Cloud context ────────────────────────────────────────────── +# Region where your Code Engine project lives (e.g. us-south, eu-de, au-syd) +IBMCLOUD_REGION=us-south + +# Resource group that owns the project (often “default”) +IBMCLOUD_RESOURCE_GROUP=default + +# Code Engine project name +IBMCLOUD_PROJECT=my-codeengine-project + +# Desired application name in Code Engine +IBMCLOUD_CODE_ENGINE_APP=mcpgateway + + +# ── Container image details ───────────────────────────────────────────── +# Fully-qualified tag in IBM Container Registry +IBMCLOUD_IMAGE_NAME=us.icr.io/myspace/mcpgateway:latest + +# Local tag produced by `make podman` or `make docker` +IBMCLOUD_IMG_PROD=mcpgateway/mcpgateway + + +# ── Authentication ────────────────────────────────────────────────────── +# IAM API key for scripted logins. +# Leave **blank** if you prefer interactive SSO (`ibmcloud login --sso`) +IBMCLOUD_API_KEY= + + +# ── Compute sizing (must match a valid CPU/MEM combo) ─────────────────── +# Reference table: https://cloud.ibm.com/docs/codeengine?topic=codeengine-mem-cpu-combo +IBMCLOUD_CPU=1 +IBMCLOUD_MEMORY=4G + + +# ── Registry secret ───────────────────────────────────────────────────── +# Name for the pull secret Code Engine uses to fetch from ICR +IBMCLOUD_REGISTRY_SECRET=my-regcred diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..d0ec9fb86f --- /dev/null +++ b/.env.example @@ -0,0 +1,228 @@ +##################################### +# Basic Application Settings +##################################### + +# The public-facing name of your gateway (used in docs/UI) +APP_NAME=MCP_Gateway + +# Bind address for HTTP server (0.0.0.0 for all interfaces) +HOST=0.0.0.0 + +# TCP port to listen on +PORT=4444 + +# SQLAlchemy connection string; leave as-is for local dev +# Examples: +# sqlite:///./mcp.db (local file-based) +# postgresql://user:pass@localhost:5432/mcp +DATABASE_URL=sqlite:///./mcp.db + +# Optional root path for deployments under a subpath (e.g., /api) +# Leave empty for default (/) +# Example: APP_ROOT_PATH=/gateway +APP_ROOT_PATH= + +##################################### +# Database Connection Pooling +##################################### + +# Size of the SQLAlchemy connection pool (default is 200) +DB_POOL_SIZE=200 + +# Number of extra connections allowed beyond pool size +DB_MAX_OVERFLOW=10 + +# Max time (in seconds) to wait for a connection from pool +DB_POOL_TIMEOUT=30 + +# Recycle database connections after N seconds +DB_POOL_RECYCLE=3600 + +##################################### +# Cache Backend +##################################### + +# Backend for caching (memory, redis, database, or none) +CACHE_TYPE=database + +# If using Redis: provide full connection URL +# Example: redis://localhost:6379/0 +REDIS_URL=redis://localhost:6379/0 + +# Prefix for cache keys (recommended to avoid collisions) +CACHE_PREFIX=mcpgw: + +# TTL for user sessions (in seconds) +SESSION_TTL=3600 + +# TTL for ephemeral messages (like completions) in seconds +MESSAGE_TTL=600 + +##################################### +# Protocol Settings +##################################### + +# MCP protocol version supported by this gateway +PROTOCOL_VERSION=2025-03-26 + +##################################### +# Authentication +##################################### + +# Admin UI basic-auth credentials +BASIC_AUTH_USER=admin +BASIC_AUTH_PASSWORD=changeme + +# Require any form of authentication (true or false) +AUTH_REQUIRED=true + +# Secret used to sign JWTs (use long random value in prod) +JWT_SECRET_KEY=my-test-key + +# Algorithm used to sign JWTs (e.g., HS256) +JWT_ALGORITHM=HS256 + +# Expiry time for generated JWT tokens (in minutes; e.g. 7 days) +TOKEN_EXPIRY=10080 + +# Used to derive an AES encryption key for secure auth storage +# Must be a non-empty string (e.g. passphrase or random secret) +AUTH_ENCRYPTION_SECRET=my-test-salt + +##################################### +# Admin UI and API Toggles +##################################### + +# Enable the visual Admin UI (true/false) +MCPGATEWAY_UI_ENABLED=true + +# Enable the Admin API endpoints (true/false) +MCPGATEWAY_ADMIN_API_ENABLED=true + +##################################### +# Security and CORS +##################################### + +# Skip TLS certificate checks for upstream requests (not recommended in prod) +SKIP_SSL_VERIFY=false + +# CORS origin allowlist (use JSON array of URLs) +# Example: ["http://localhost:3000"] +# Do not quote this value. Start with [] to ensure it's valid JSON. +ALLOWED_ORIGINS='["http://localhost", "http://localhost:4444"]' + +# Enable CORS handling in the gateway +CORS_ENABLED=true + +##################################### +# Logging +##################################### + +# Logging verbosity level: DEBUG, INFO, WARNING, ERROR, CRITICAL +LOG_LEVEL=INFO + +# Log format: json or text +LOG_FORMAT=json + +# Optional path to write logs to file (leave empty to log to stdout) +#LOG_FILE=./logs/gateway.log + +##################################### +# Transport Configuration +##################################### + +# Transport mechanisms to expose (comma-separated): http, ws, sse, stdio, all +TRANSPORT_TYPE=all + +# WebSocket ping interval (seconds) +WEBSOCKET_PING_INTERVAL=30 + +# SSE client retry timeout (milliseconds) +SSE_RETRY_TIMEOUT=5000 + +##################################### +# Federation +##################################### + +# Enable federated mode (discovery + peer syncing) +FEDERATION_ENABLED=true + +# Automatically discover peer gateways (e.g., via mDNS) +FEDERATION_DISCOVERY=false + +# Explicit list of peers (JSON array of URLs). Do not quote this value. +FEDERATION_PEERS='[]' + +# Timeout for federated requests (in seconds) +FEDERATION_TIMEOUT=30 + +# Frequency of syncing peers (in seconds) +FEDERATION_SYNC_INTERVAL=300 + +##################################### +# Resources +##################################### + +# Maximum number of resources to keep in memory cache +RESOURCE_CACHE_SIZE=1000 + +# Time-to-live for resource cache (seconds) +RESOURCE_CACHE_TTL=3600 + +# Maximum allowed size for a resource (in bytes, e.g., 10MB) +MAX_RESOURCE_SIZE=10485760 + +##################################### +# Tools +##################################### + +# Max execution time for tools (in seconds) +TOOL_TIMEOUT=60 + +# Number of retry attempts for failed tools +MAX_TOOL_RETRIES=3 + +# Max number of tool invocations per minute +TOOL_RATE_LIMIT=100 + +# Number of tools that can run simultaneously +TOOL_CONCURRENT_LIMIT=10 + +##################################### +# Prompts +##################################### + +# How many prompt templates to cache +PROMPT_CACHE_SIZE=100 + +# Max size (in bytes) for a prompt template +MAX_PROMPT_SIZE=102400 + +# Timeout for rendering prompt templates (in seconds) +PROMPT_RENDER_TIMEOUT=10 + +##################################### +# Health Checks +##################################### + +# How often to poll peer gateways (in seconds) +HEALTH_CHECK_INTERVAL=60 + +# Timeout for a single health check request (seconds) +HEALTH_CHECK_TIMEOUT=10 + +# Number of failed checks before marking peer unhealthy +UNHEALTHY_THRESHOLD=3 + +##################################### +# Development Settings +##################################### + +# Enable dev mode features (e.g. schema debug) +DEV_MODE=false + +# Reload app on code changes (dev only) +RELOAD=false + +# Enable verbose logging/debug traces +DEBUG=false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..a80fce32a9 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "root": true, + "env": { + "browser": true, + "es2021": true + }, + "extends": ["standard"], + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "rules": { + "semi": ["error", "always"], + "quotes": ["error", "double"] + } +} diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..aedc65bad0 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 600 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..144ddf9c6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,197 @@ +packages-lock.json +packages.json +node_modules/ +./depsort* +./depupdate* +.tmp* +mcp.db-journal +certs/ +FIXMEs +*.old +logs/ +*.log +.venv +mcp.db +public/ +ica_integrations_host.sbom.json +.pyre +dictionary.dic +pdm.lock +.pdm-python +temp/ +public/ +*history.md +htmlcov +test_commands.md +cover.md +build/ +.icaenv +commands_output.txt +commands_output.md +command_output.md +command_output.txt +command_outputs.md +.env +.env.ce +.ica.env +./snakefood.png +./snakefood.svg + +.attic +.vscode/settings.json +scribeflow.log +coverage_re +bin/flagged +flagged/ +certs/ +# VENV +.python37/ +.python39/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Bak +*.bak + +# C extensions +*.so + +# Distribution / packaging +.wily/ +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# others +.pdm-build +.vscode + +# Mac junk +.DS_Store + +# MYPY cache +.mypy_cache +.idea/ + +# Sonar +.scannerwork + +# vim +*.swp +*,cover + +# model cache +.config +logging/ + +.ai* + +# downloads +downloads/ + +# db_path +db_path/ + +.continue diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000000..ff073815f2 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,2 @@ +ignored: + - SC1019 diff --git a/.htmlhintrc b/.htmlhintrc new file mode 100644 index 0000000000..0c1c2fe0bb --- /dev/null +++ b/.htmlhintrc @@ -0,0 +1,7 @@ +{ + "attr-value-double-quotes": true, + "tag-pair": true, + "doctype-first": true, + "id-unique": true, + "spec-char-escape": true +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..538c3e25ed --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,218 @@ +# https://pre-commit.com/hooks.html +# pip3 install --user --upgrade pre-commit; pre-commit install; pre-commit run -a +# git commit -m "don't verify my stuff" --no-verify +--- +repos: +- repo: https://github.com/gitleaks/gitleaks + rev: v8.24.2 + hooks: + - id: gitleaks +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + name: check for added large files + description: prevents giant files from being committed. + entry: check-added-large-files + language: python + stages: [pre-commit, pre-push, manual] + - id: check-ast + name: check python ast + description: simply checks whether the files parse as valid python. + entry: check-ast + language: python + types: [python] + exclude: ^templates/ + - id: check-byte-order-marker + name: 'check BOM - deprecated: use fix-byte-order-marker' + description: forbids files which have a utf-8 byte-order marker. + entry: check-byte-order-marker + language: python + types: [text] + - id: check-builtin-literals + name: check builtin type constructor use + description: requires literal syntax when initializing empty or zero python builtin types. + entry: check-builtin-literals + language: python + types: [python] + exclude: ^templates/ + - id: check-case-conflict + name: check for case conflicts + description: checks for files that would conflict in case-insensitive filesystems. + entry: check-case-conflict + language: python + - id: check-docstring-first + name: check docstring is first + description: checks a common error of defining a docstring after code. + entry: check-docstring-first + language: python + types: [python] + - id: check-executables-have-shebangs + name: check that executables have shebangs + description: ensures that (non-binary) executables have a shebang. + entry: check-executables-have-shebangs + language: python + types: [text, executable] + stages: [pre-commit, pre-push, manual] + - id: check-json + name: check json + description: checks json files for parseable syntax. + entry: check-json + language: python + types: [json] + - id: check-shebang-scripts-are-executable + name: check that scripts with shebangs are executable + description: ensures that (non-binary) files with a shebang are executable. + entry: check-shebang-scripts-are-executable + language: python + types: [text] + stages: [pre-commit, pre-push, manual] + # - id: pretty-format-json + # name: pretty format json + # description: sets a standard for formatting json files. + # entry: pretty-format-json + # language: python + # types: [json] + - id: check-merge-conflict + name: check for merge conflicts + description: checks for files that contain merge conflict strings. + entry: check-merge-conflict + language: python + types: [text] + - id: check-symlinks + name: check for broken symlinks + description: checks for symlinks which do not point to anything. + entry: check-symlinks + language: python + types: [symlink] + - id: check-toml + name: check toml + description: checks toml files for parseable syntax. + entry: check-toml + language: python + types: [toml] + - id: check-vcs-permalinks + name: check vcs permalinks + description: ensures that links to vcs websites are permalinks. + entry: check-vcs-permalinks + language: python + types: [text] + - id: check-xml + name: check xml + description: checks xml files for parseable syntax. + entry: check-xml + language: python + types: [xml] + - id: check-yaml + name: check yaml + description: checks yaml files for parseable syntax. + entry: check-yaml + language: python + types: [yaml] + exclude: ^docs/ + - id: debug-statements + name: debug statements (python) + description: checks for debugger imports and py37+ `breakpoint()` calls in python source. + entry: debug-statement-hook + language: python + types: [python] + exclude: ^templates/ + - id: destroyed-symlinks + name: detect destroyed symlinks + description: detects symlinks which are changed to regular files with a content of a path which that symlink was pointing to. + entry: destroyed-symlinks + language: python + types: [file] + - id: detect-aws-credentials + name: detect aws credentials + description: detects *your* aws credentials from the aws cli credentials file. + entry: detect-aws-credentials + language: python + types: [text] + - id: detect-private-key + name: detect private key + description: detects the presence of private keys. + entry: detect-private-key + language: python + types: [text] + # - id: double-quote-string-fixer + # name: fix double quoted strings + # description: replaces double quoted strings with single quoted strings. + # entry: double-quote-string-fixer + # language: python + # types: [python] + - id: end-of-file-fixer + name: fix end of files + description: ensures that a file is either empty, or ends with one newline. + entry: end-of-file-fixer + language: python + types: [text] + stages: [pre-commit, pre-push, manual] + - id: file-contents-sorter + name: file contents sorter + description: sorts the lines in specified files (defaults to alphabetical). you must provide list of target files as input in your .pre-commit-config.yaml file. + entry: file-contents-sorter + language: python + files: '^$' + - id: fix-byte-order-marker + name: fix utf-8 byte order marker + description: removes utf-8 byte order marker. + entry: fix-byte-order-marker + language: python + types: [text] + - id: fix-encoding-pragma + name: fix python encoding pragma + description: 'adds # -*- coding: utf-8 -*- to the top of python files.' + language: python + entry: fix-encoding-pragma + types: [python] + - id: forbid-new-submodules + name: forbid new submodules + description: prevents addition of new git submodules. + language: python + entry: forbid-new-submodules + types: [directory] + - id: forbid-submodules + name: forbid submodules + description: forbids any submodules in the repository + language: fail + entry: 'submodules are not allowed in this repository:' + types: [directory] + - id: mixed-line-ending + name: mixed line ending + description: replaces or checks mixed line ending. + entry: mixed-line-ending + language: python + types: [text, python] + args: [--fix=lf] + - id: name-tests-test + name: python tests naming + description: verifies that test files are named correctly. + entry: name-tests-test + language: python + files: (^|/)tests/.+\.py$ + - id: no-commit-to-branch + name: "don't commit to branch" + entry: no-commit-to-branch + language: python + pass_filenames: false + always_run: true + - id: requirements-txt-fixer + name: fix requirements.txt + description: sorts entries in requirements.txt. + entry: requirements-txt-fixer + language: python + files: (requirements|constraints).*\.txt$ + - id: sort-simple-yaml + name: sort simple yaml files + description: sorts simple yaml files which consist only of top-level keys, preserving comments and blocks. + language: python + entry: sort-simple-yaml + files: '^$' + - id: trailing-whitespace + name: trim trailing whitespace + description: trims trailing whitespace. + entry: trailing-whitespace-fixer + language: python + types: [text] + stages: [pre-commit, pre-push, manual] diff --git a/.pycodestyle b/.pycodestyle new file mode 100644 index 0000000000..b194eb8823 --- /dev/null +++ b/.pycodestyle @@ -0,0 +1,5 @@ +[pycodestyle] +max-line-length = 200 +#count = False +#ignore = E226,E302,E71 +statistics = True diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..233cdc857d --- /dev/null +++ b/.pylintrc @@ -0,0 +1,649 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.9 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=e, + ex, + bind, + workers, + loglevel, + timeout, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=12 +max-positional-arguments = 6 + +# Maximum number of attributes for a class (see R0902). +max-attributes=10 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=7 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=200 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=new + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + too-many-locals, + too-many-arguments, + import-error, + logging-fstring-interpolation, + broad-exception-caught, + broad-exception-raised, + raise-missing-from, + too-few-public-methods, + line-too-long, + too-many-lines, + too-many-branches, + too-many-statements, + too-many-public-methods + +# TODO: remove most of the disabled onews above + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=8 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work.. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/.pyspelling.yml b/.pyspelling.yml new file mode 100644 index 0000000000..7d1bc51ea1 --- /dev/null +++ b/.pyspelling.yml @@ -0,0 +1,28 @@ +matrix: +- name: markdown + aspell: + lang: en + d: en_US + camel-case: true + mode: markdown + sources: + - "src/*|!CHANGELOG.md" + - "docs/*|!CHANGELOG.md" + - "*.md" + dictionary: + wordlists: + - .spellcheck-en.txt + pipeline: + - pyspelling.filters.context: + context_visible_first: true + escapes: '\\[\\`~]' + delimiters: + # Ignore multiline content between fences (fences can have 3 or more back ticks) + # ```language + # content + # ``` + - open: '(?s)^(?P *`{3,}).*$' + close: '^(?P=open)$' + # Ignore text between inline back ticks + - open: '(?P`+)' + close: '(?P=open)' diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000000..443a275df1 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,63 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "docs", + "test" +] + +# 200 line length +line-length = 200 +indent-width = 4 + +# Assume Python 3.11 +target-version = "py311" + +[lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" diff --git a/.spellcheck-en.txt b/.spellcheck-en.txt new file mode 100644 index 0000000000..490603857c --- /dev/null +++ b/.spellcheck-en.txt @@ -0,0 +1,440 @@ +addr +ai +aimodels +ansible +AoT +AoT +AoT +api +argparse +argparse +args +args +arithmatex +asctime +aspell +async +atm +attr +Auth +backoff +bam +BAM +bcg +betterem +blockdiag +bracex +changelog +cli +CLI +cmihai +commandline +config +consitent +consultingassistants + +CORS +CP +CP +CP +Criveti +crmihai +CSPM +CSPM +CSPM +css +csv +CustomFormatter +CustomFormatter +cvvf +CWPP +CWPP +CWPP +DAST +DAST +DAST +datagenerator +datefmt +dev +dir +docbuilder +docstring +doctest +docx +DOCX +duckduckgo +env +epilog +epilog +Errno +errorcode +escapeall +failover +fastapi +fastapi +favicon +fawltydeps +filepath +filepath +FISMA +FISMA +FISMA +fixme +fontawesome +formatter +formatter +FS +FS +FS +FTE +FTE +FTE +funcName +GCP +GCP +GCP +GDPR +GDPR +GDPR +geministt +Gemmist +gh +gh +github +github +github +gitignore +GPE +GPE +GPE +gunicorn +hardbreak +HIPAA +HIPAA +HIPAA +HSM +HSM +HSM +html +html +html +html +IaaS +IaaS +IaaS +IAM +IAM +IAM +IAST +IAST +IAST +ibm +ibm +IC +IC +IC +ica +ica +icacli +icacli +ICAClient +icamock +ICCA +ICCA +ICCA +ico +idna +iframe +IFRAME +img +init +inlinehilite +instagram +integrations +Integrations +interactiv +isdigit +isort +ISV +ISV +ISV +javascript +Jinja +jitter +js +js +json +JSON +JTC +JTC +JTC +jupyter +jupyter +Jupyter +KMS +KMS +KMS +Kubernetes +Kubernetes +Kubernetes +lang +langchain +langchain +Langchain +lc +lc +levelname +libica +libica +lifecycle +lineno +linenums +linters +llamaindex +llm +LLM +llms +llms +Llms +Llms +Llms +Llms +Llms +Llms +magiclink +Makefile +manpage +MCP +MCP +md +md +md +MERCHANTABILITY +MERCHANTABILITY +microservice +microservice +Mihai +minify +mkdir +mkdocs +mkdocs +mkdocs +MKDOCS +mkdocscombine +mktmp +monkeypatched +monkeypatched +MRC +MRC +MRC +MSP +MSP +MSP +MSS +MSS +MSS +mv +MYINTEGRATION +mypy +NBIE +NBIE +NBIE +nbsp +NONINFRINGEMENT +NONINFRINGEMENT +OCI +OCI +OCI +oic +oic +OIC +OIC +OIC +Onboarding +Onboarding +Onboarding +ORM +os +OSINT +OSINT +OSINT +PaaS +PaaS +PaaS +Pak +Pak +Pak +pandoc +params +params +PCI +PCI +PCI +pdf +pdf +permalink +PES +PES +PES +php +Plex +png +png +podman +pptx +PPTX +pre +Profiler +Profiler +progressbar +pwd +py +pycon +pycparser +pydantic +Pydantic +pydocstyle +pygments +pylint +Pylint +pymdownx +pymdownx +pypi +pyproject +pyright +pyroma +pyspelling +pyspelling +pytest +PYTHONPATH +pytz +pyyaml +qareact +reconnection +REPL +repo +repo +RESTful +rf +RFS +RFS +RFS +ROKS +ROKS +ROKS +RPC +RTM +RTM +RTM +SaaS +SaaS +SaaS +SAST +SAST +SAST +SBU +SBU +SBU +SDK +securitykey +sed +setuptools +shortener +SKU +SKU +SKU +SLA +SLA +SLA +SLO +SLO +SLO +smartsymbols +smocker +soupsieve +src +SSO +SSO +SSO +SSPM +SSPM +SSPM +startinline +stderr +stdout +Stmts +Stmts +str +str +STR +striphtml +stylesheet +stylesheets +sublicense +sublicense +summarizer +Summarizer +superfences +svg +sys +systemprompt +tasklist +templating +tmp +toc +toc +TOC +TODO +TODO.md +toml +toplevel +TTL +TTP +TTP +TTP +txt +tzdata +uff +UI +uml +un +univocation +univocation +untracked +uri +url +url +utf +uuid +uuid +validator +venv +venv +VENVS +VMS +VMS +VMS +VMWare +VMWare +VMWare +VPC +VPC +VPC +VRW +VRW +VRW +watsonx +WBS +WBS +WBS +weasyprint +webcolors +webex +Webex +wikipedia +wyre +XFR +XFR +XFR +yml +yml diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000000..13d3c02032 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "no-descending-specificity": null + } +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..9ea172d2a7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +sudo: false +language: python +python: + - 3.11 +services: + - docker +os: + - linux +before_install: + - rm -f ~/.netrc + - sudo cat /etc/apt/sources.list; sudo ls /etc/apt/sources.list.d/ + - sudo sed -i '/erlang/s/^/#/' /etc/apt/sources.list.d/*.list + - sudo apt-get -qq update || true + - sudo apt-get install -y aspell aspell-en || true + - make venv + - source ${HOME}/.venv/mcpgateway/bin/activate + - pip install .[dev] +jobs: + include: + - stage: build + script: + - make lint | tee docs/docs/test/lint.md + - stage: build + script: + - docker build -f Containerfile --platform=linux/amd64 -t mcpgateway/mcpgateway . + - docker images + - docker run --name mcpgateway -p 4444:4444 -d mcpgateway/mcpgateway + - docker ps -a && sleep 10 + - curl -X POST http://localhost:4444/servers -u admin:changeme + - docker logs mcpgateway +git: + depth: false diff --git a/.whitesource b/.whitesource new file mode 100644 index 0000000000..f24a084669 --- /dev/null +++ b/.whitesource @@ -0,0 +1 @@ +{"settingsInheritedFrom": "whitesource-config/whitesource-config@master", "scanSettings": {"configMode": "LOCAL"}} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..a2f031a320 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +## Contributing In General +Our project welcomes external contributions. If you have an itch, please feel +free to scratch it. + +To contribute code or documentation, please submit a **FIXME** [pull request](https://github.com/ibm/mcp-context-forge/pulls). + +A good way to familiarize yourself with the codebase and contribution process is +to look for and tackle low-hanging fruit in the **FIXME** [issue tracker](https://github.com/ibm/mcp-context-forge/issues). +Before embarking on a more ambitious contribution, please quickly [get in touch](#communication) with us. + +**Note: We appreciate your effort, and want to avoid a situation where a contribution +requires extensive rework (by you or by us), sits in backlog for a long time, or +cannot be accepted at all!** + +### Proposing new features + +If you would like to implement a new feature, please **FIXME** [raise an issue](https://github.com/ibm/mcp-context-forge/issues) +before sending a pull request so the feature can be discussed. This is to avoid +you wasting your valuable time working on a feature that the project developers +are not interested in accepting into the code base. + +### Fixing bugs + +If you would like to fix a bug, please **FIXME** [raise an issue](https://github.com/ibm/mcp-context-forge/issues) before sending a +pull request so it can be tracked. + +### Merge approval + +The project maintainers use LGTM (Looks Good To Me) in comments on the code +review to indicate acceptance. A change requires LGTMs from two of the +maintainers of each component affected. + +For a list of the maintainers, see the [MAINTAINERS.md](MAINTAINERS.md) page. + +## Legal + +Each source file must include a license header for the Apache +Software License 2.0. Using the SPDX format is the simplest approach. +e.g. + +``` +/* +Copyright All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +``` + +We have tried to make it as easy as possible to make contributions. This +applies to how we handle the legal aspects of contribution. We use the +same approach - the [Developer's Certificate of Origin 1.1 (DCO)](https://github.com/hyperledger/fabric/blob/master/docs/source/DCO1.1.txt) - that the Linux® Kernel [community](https://elinux.org/Developer_Certificate_Of_Origin) +uses to manage code contributions. + +We simply ask that when submitting a patch for review, the developer +must include a sign-off statement in the commit message. + +Here is an example Signed-off-by line, which indicates that the +submitter accepts the DCO: + +``` +Signed-off-by: John Doe +``` + +You can include this automatically when you commit a change to your +local git repository using the following command: + +``` +git commit -s +``` + +## Communication +**FIXME** Please feel free to connect with us on our [Slack channel](link). + +## Setup +**FIXME** Please add any special setup instructions for your project to help the developer +become productive quickly. + +## Testing +**FIXME** Please provide information that helps the developer test any changes they make +before submitting. + +## Coding style guidelines +**FIXME** Optional, but recommended: please share any specific style guidelines you might +have for your project. diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000000..06107f6675 --- /dev/null +++ b/Containerfile @@ -0,0 +1,40 @@ +FROM registry.access.redhat.com/ubi9-minimal:9.6-1747218906 +LABEL maintainer="Mihai Criveti" \ + name="mcp/mcpgateway" \ + description="MCP Gateway: An enterprise-ready Model Context Protocol Gateway" + +ARG PYTHON_VERSION=3.11 + +# Install Python and build dependencies +RUN microdnf update -y && \ + microdnf install -y python${PYTHON_VERSION} python${PYTHON_VERSION}-devel gcc git && \ + microdnf clean all + +# Set default python3 to the specified version +RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 + +WORKDIR /app + +# Copy project files into container +COPY . /app + +# Create virtual environment, upgrade pip and install dependencies using uv for speed +RUN python3 -m venv /app/.venv && \ + /app/.venv/bin/python3 -m pip install --upgrade pip setuptools pdm uv && \ + /app/.venv/bin/python3 -m uv pip install . + +# update the user permissions +RUN chown -R 1001:0 /app && \ + chmod -R g=u /app + +# Expose the application port +EXPOSE 4444 + +# Set the runtime user +USER 1001 + +# Ensure virtual environment binaries are in PATH +ENV PATH="/app/.venv/bin:$PATH" + +# Start the application using run-gunicorn.sh +CMD ["./run-gunicorn.sh"] diff --git a/Containerfile.lite b/Containerfile.lite new file mode 100644 index 0000000000..031a390356 --- /dev/null +++ b/Containerfile.lite @@ -0,0 +1,136 @@ +# syntax=docker/dockerfile:1.7 + +############################################################################### +# MCP Gateway (lite) – OCI-compliant container build +# +# This multi-stage Dockerfile produces an ultra-slim, scratch-based runtime +# image that automatically tracks the latest Python 3.11.x patch release +# from the RHEL 9 repositories and is fully patched on each rebuild. +# +# Key design points: +# • Builder stage has full DNF + devel headers for wheel compilation +# • Runtime stage is scratch: only the Python runtime and app +# • Both builder and runtime rootfs receive `dnf upgrade -y` +# • Development headers are dropped from the final image +# • Hadolint DL3041 is suppressed to allow "latest patch" RPM usage +############################################################################### + +########################### +# Build-time arguments +########################### +ARG ROOTFS_PATH=/tmp/rootfs # Temporary dir for assembling the scratch rootfs +ARG PYTHON_VERSION=3.11 # Python major.minor series to track + +########################### +# Base image for copying into scratch +########################### +FROM registry.access.redhat.com/ubi9/ubi-micro:9.6-1747318857 AS base + +########################### +# Builder stage +########################### +FROM registry.access.redhat.com/ubi9/ubi:9.6-1747219013 AS builder + +ARG PYTHON_VERSION +ARG ROOTFS_PATH + +# ---------------------------------------------------------------------------- +# 1) Patch the OS +# 2) Install Python + headers for building wheels +# 3) Register python3 alternative +# 4) Clean caches to reduce layer size +# ---------------------------------------------------------------------------- +# hadolint ignore=DL3041 +RUN set -euo pipefail \ + && dnf upgrade -y \ + && dnf install -y \ + python${PYTHON_VERSION} \ + python${PYTHON_VERSION}-devel \ + && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \ + && dnf clean all + +WORKDIR /app + +# Copy project source last so small code changes don't bust earlier caches +COPY . /app + +# ---------------------------------------------------------------------------- +# Create and populate virtual environment +# - Upgrade pip, setuptools, wheel, pdm, uv +# - Install project dependencies and package +# - Remove build caches +# ---------------------------------------------------------------------------- +RUN set -euo pipefail \ + && python3 -m venv /app/.venv \ + && /app/.venv/bin/pip install --no-cache-dir --upgrade pip setuptools wheel pdm uv \ + && /app/.venv/bin/uv pip install . \ + && rm -rf /root/.cache /var/cache/dnf + +# ---------------------------------------------------------------------------- +# Build a minimal, fully-patched rootfs containing only the runtime Python +# ---------------------------------------------------------------------------- +# hadolint ignore=DL3041 +RUN set -euo pipefail \ + && mkdir -p "${ROOTFS_PATH}" \ + && dnf --installroot="${ROOTFS_PATH}" --releasever=9 upgrade -y \ + && dnf --installroot="${ROOTFS_PATH}" --releasever=9 install -y \ + --setopt=install_weak_deps=0 \ + python${PYTHON_VERSION} \ + && dnf clean all --installroot="${ROOTFS_PATH}" + +########################### +# Runtime stage +########################### +FROM scratch AS runtime + +ARG PYTHON_VERSION +ARG ROOTFS_PATH + +# ---------------------------------------------------------------------------- +# OCI image metadata +# ---------------------------------------------------------------------------- +LABEL maintainer="Mihai Criveti" \ + org.opencontainers.image.title="mcp/mcpgateway" \ + org.opencontainers.image.description="MCP Gateway: An enterprise-ready Model Context Protocol Gateway" \ + org.opencontainers.image.licenses="Apache-2.0" + +# ---------------------------------------------------------------------------- +# Populate the scratch filesystem +# - Copy in the minimal Python runtime +# - Copy in the application directory +# ---------------------------------------------------------------------------- +COPY --from=builder ${ROOTFS_PATH}/ / +COPY --from=builder /app /app + +# ---------------------------------------------------------------------------- +# Create `python3` symlink for compatibility +# ---------------------------------------------------------------------------- +RUN ln -s /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 + +# ---------------------------------------------------------------------------- +# Fix permissions for non-root user (1001) and OpenShift compatibility +# ---------------------------------------------------------------------------- +RUN chown -R 1001:0 /app && chmod -R g=u /app + +# Application working directory +WORKDIR /app + +# ---------------------------------------------------------------------------- +# Expose application port +# ---------------------------------------------------------------------------- +EXPOSE 4444 + +# ---------------------------------------------------------------------------- +# Run as non-root user +# ---------------------------------------------------------------------------- +USER 1001 + +# ---------------------------------------------------------------------------- +# Ensure our virtual environment binaries have priority in PATH +# ---------------------------------------------------------------------------- +ENV PATH="/app/.venv/bin:${PATH}" + +# ---------------------------------------------------------------------------- +# Entrypoint +# ---------------------------------------------------------------------------- +CMD ["./run-gunicorn.sh"] diff --git a/DEVELOPING.md b/DEVELOPING.md new file mode 100644 index 0000000000..4ca869250d --- /dev/null +++ b/DEVELOPING.md @@ -0,0 +1,24 @@ +## Development Testing with MCP Inspector + +``` +export MCP_GATEWAY_BASE_URL=http://localhost:4444 +export MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/1 +export MCP_AUTH_USER=admin +export MCP_AUTH_PASS=changeme + + +npx @modelcontextprotocol/inspector # SSE +npx @modelcontextprotocol/inspector uv --directory "/home/cmihai/mcpgateway-wrapper" run mcpgateway-wrapper # wrapper +``` + +🔍 MCP Inspector is up and running at http://localhost:5173 🚀 + + +## SuperGateway + +Supergateway runs a MCP stdio-based servers over SSE (Server-Sent Events) with one command. This is useful for remote access, debugging, or connecting to SSE-based clients when your MCP server only speaks stdio. + +`npx -y supergateway --stdio "uvx mcp-server-git"`` + +SSE endpoint: GET http://localhost:8000/sse +POST messages: POST http://localhost:8000/message diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..4b0c2b7eed --- /dev/null +++ b/Makefile @@ -0,0 +1,1063 @@ +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# 🐍 MCP CONTEXT FORGE - Makefile +# (An enterprise-ready Model Context Protocol Gateway) +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# +# Author: Mihai Criveti +# Description: Build & automation helpers for the MCP Gateway project +# Usage: run `make` or `make help` to view available targets +# +# help: 🐍 MCP CONTEXT FORGE (An enterprise-ready Model Context Protocol Gateway) +# +# ────────────────────────────────────────────────────────────────────────── +# Project variables +PROJECT_NAME = mcpgateway +DOCS_DIR = docs +HANDSDOWN_PARAMS = -o $(DOCS_DIR)/ -n $(PROJECT_NAME) --name "MCP Gateway" --cleanup + +TEST_DOCS_DIR ?= $(DOCS_DIR)/docs/test + +# Project-wide clean-up targets +DIRS_TO_CLEAN := __pycache__ .pytest_cache .tox .ruff_cache .pyre .mypy_cache .pytype \ + dist build site .eggs *.egg-info .cache htmlcov certs \ + $(VENV_DIR).sbom $(COVERAGE_DIR) \ + node_modules + +FILES_TO_CLEAN := .coverage coverage.xml mcp.prof mcp.pstats \ + $(PROJECT_NAME).sbom.json \ + snakefood.dot packages.dot classes.dot \ + $(DOCS_DIR)/pstats.png \ + $(DOCS_DIR)/docs/test/sbom.md \ + $(DOCS_DIR)/docs/test/{unittest,full,index,test}.md \ + $(DOCS_DIR)/docs/images/coverage.svg $(LICENSES_MD) $(METRICS_MD) + +COVERAGE_DIR ?= $(DOCS_DIR)/docs/coverage +LICENSES_MD ?= $(DOCS_DIR)/docs/test/licenses.md +METRICS_MD ?= $(DOCS_DIR)/docs/metrics/loc.md + +# ----------------------------------------------------------------------------- +# Container resource configuration +CONTAINER_MEMORY = 2048m +CONTAINER_CPUS = 2 + +# Virtual-environment variables +VENVS_DIR := $(HOME)/.venv +VENV_DIR := $(VENVS_DIR)/$(PROJECT_NAME) + +# ============================================================================= +# 📖 DYNAMIC HELP +# ============================================================================= +.PHONY: help +help: + @grep "^# help\:" Makefile | grep -v grep | sed 's/\# help\: //' | sed 's/\# help\://' + +# ----------------------------------------------------------------------------- +# 🔧 SYSTEM-LEVEL DEPENDENCIES +# ----------------------------------------------------------------------------- +# help: 🔧 SYSTEM-LEVEL DEPENDENCIES (DEV BUILD ONLY) +# help: os-deps - Install Graphviz, Pandoc, Trivy, SCC used for dev docs generation and security scan +OS_DEPS_SCRIPT := ./os_deps.sh + +.PHONY: os-deps +os-deps: $(OS_DEPS_SCRIPT) + @bash $(OS_DEPS_SCRIPT) + + +# ============================================================================= +# 🌱 VIRTUAL ENVIRONMENT & INSTALLATION +# ============================================================================= +# help: 🌱 VIRTUAL ENVIRONMENT & INSTALLATION +# help: venv - Create a fresh virtual environment with uv & friends +# help: activate - Activate the virtual environment in the current shell +# help: install - Install project into the venv +# help: install-dev - Install project (incl. dev deps) into the venv +# help: update - Update all installed deps inside the venv +.PHONY: venv +venv: + @rm -Rf "$(VENV_DIR)" + @test -d "$(VENVS_DIR)" || mkdir -p "$(VENVS_DIR)" + @python3 -m venv "$(VENV_DIR)" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m pip install --upgrade pip setuptools pdm uv" + @echo -e "✅ Virtual env created.\n💡 Enter it with:\n . $(VENV_DIR)/bin/activate\n" + +.PHONY: activate +activate: + @echo -e "💡 Enter the venv using:\n . $(VENV_DIR)/bin/activate\n" + @. $(VENV_DIR)/bin/activate + @echo "export MYPY_CACHE_DIR=/tmp/cache/mypy/$(PROJECT_NAME)" + @echo "export PYTHONPYCACHEPREFIX=/tmp/cache/$(PROJECT_NAME)" + +.PHONY: install +install: venv + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install ." + +.PHONY: install-dev +install-dev: venv + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install .[dev]" + +.PHONY: update +update: + @echo "⬆️ Updating installed dependencies…" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install -U .[dev]" + +# help: check-env - Verify all required env vars in .env are present +.PHONY: check-env +check-env: + @echo "🔎 Checking .env against .env.example…" + @missing=0; \ + for key in $$(grep -Ev '^\s*#|^\s*$$' .env.example | cut -d= -f1); do \ + grep -q "^$$key=" .env || { echo "❌ Missing: $$key"; missing=1; }; \ + done; \ + if [ $$missing -eq 0 ]; then echo "✅ All environment variables are present."; fi + + +# ============================================================================= +# ▶️ SERVE & TESTING +# ============================================================================= +# help: ▶️ SERVE & TESTING +# help: serve - Run production Gunicorn server on :4444 +# help: certs - Generate self-signed TLS cert & key in ./certs (won't overwrite) +# help: serve-ssl - Run Gunicorn behind HTTPS on :4444 (uses ./certs) +# help: dev - Run fast-reload dev server (uvicorn) +# help: run - Execute helper script ./run.sh +# help: test - Run unit tests with pytest +# help: test-curl - Smoke-test API endpoints with curl script +# help: pytest-examples - Run README / examples through pytest-examples + +.PHONY: serve serve-ssl dev run test test-curl pytest-examples certs clean + +## --- Primary servers --------------------------------------------------------- +serve: + ./run-gunicorn.sh + +serve-ssl: certs + SSL=true CERT_FILE=certs/cert.pem KEY_FILE=certs/key.pem ./run-gunicorn.sh + +dev: + uvicorn mcpgateway.main:app --reload --reload-exclude='public/' + +run: + ./run.sh + +## --- Certificate helper ------------------------------------------------------ +certs: ## Generate ./certs/cert.pem & ./certs/key.pem (idempotent) + @if [ -f certs/cert.pem ] && [ -f certs/key.pem ]; then \ + echo "🔏 Existing certificates found in ./certs – skipping generation."; \ + else \ + echo "🔏 Generating self-signed certificate (1 year)…"; \ + mkdir -p certs; \ + openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ + -keyout certs/key.pem -out certs/cert.pem \ + -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"; \ + echo "✅ TLS certificate written to ./certs"; \ + fi + chmod 640 certs/key.pem + +## --- Testing ----------------------------------------------------------------- +test: + @echo "🧪 Running tests..." + @test -d "$(VENV_DIR)" || make venv + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python -m pip install pytest pytest-asyncio pytest-cov -q && python -m pytest --maxfail=0 --disable-warnings -v" + +pytest-examples: + @echo "🧪 Testing README examples..." + @test -d "$(VENV_DIR)" || make venv + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python -m pip install pytest pytest-examples -q && pytest -v test_readme.py" + +test-curl: + ./test_endpoints.sh + +## --- House-keeping ----------------------------------------------------------- +# help: clean - Remove caches, build artefacts, virtualenv, docs, certs, coverage, SBOM, etc. +.PHONY: clean +clean: + @echo "🧹 Cleaning workspace…" + @# Remove matching directories + @for dir in $(DIRS_TO_CLEAN); do \ + find . -type d -name "$$dir" -exec rm -rf {} +; \ + done + @# Remove listed files + @rm -f $(FILES_TO_CLEAN) + @# Delete Python bytecode + @find . -name '*.py[cod]' -delete + @echo "✅ Clean complete." + + +# ============================================================================= +# 📊 COVERAGE & METRICS +# ============================================================================= +# help: 📊 COVERAGE & METRICS +# help: coverage - Run tests with coverage, emit md/HTML/XML + badge +# help: pip-licenses - Produce dependency license inventory (markdown) +# help: scc - Quick LoC/complexity snapshot with scc +# help: scc-report - Generate HTML LoC & per-file metrics with scc +.PHONY: coverage pip-licenses scc scc-report + +coverage: + @mkdir -p $(TEST_DOCS_DIR) + @printf "# Unit tests\n\n" > $(DOCS_DIR)/docs/test/unittest.md + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + python3 -m pytest -p pytest_cov --reruns=1 --reruns-delay 30 \ + --md-report --md-report-output=$(DOCS_DIR)/docs/test/unittest.md \ + --dist loadgroup -n 8 -rA --cov-append --capture=tee-sys -v \ + --durations=120 --doctest-modules app/ --cov-report=term \ + --cov=app --ignore=test.py tests/ || true" + @printf '\n## Coverage report\n\n' >> $(DOCS_DIR)/docs/test/unittest.md + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + coverage report --format=markdown -m --no-skip-covered \ + >> $(DOCS_DIR)/docs/test/unittest.md" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + coverage html -d $(COVERAGE_DIR) --include=app/*" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && coverage xml" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + coverage-badge -fo $(DOCS_DIR)/docs/images/coverage.svg" + @echo "✅ Coverage artefacts: md, HTML in $(COVERAGE_DIR), XML & badge ✔" + +pip-licenses: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install pip-licenses" + @mkdir -p $(dir $(LICENSES_MD)) + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + pip-licenses --format=markdown --with-authors --with-urls > $(LICENSES_MD)" + @cat $(LICENSES_MD) + @echo "📜 License inventory written to $(LICENSES_MD)" + +scc: + @scc --by-file -i py,sh . + +scc-report: + @mkdir -p $(dir $(METRICS_MD)) + @printf "# Lines of Code Report\n\n" > $(METRICS_MD) + @scc . --format=html-table >> $(METRICS_MD) + @printf "\n\n## Per-file metrics\n\n" >> $(METRICS_MD) + @scc -i py,sh,yaml,toml,md --by-file . --format=html-table >> $(METRICS_MD) + @echo "📊 LoC metrics captured in $(METRICS_MD)" + +# ============================================================================= +# 📚 DOCUMENTATION +# ============================================================================= +# help: 📚 DOCUMENTATION & SBOM +# help: docs - Build docs (graphviz + handsdown + images + SBOM) +# help: images - Generate architecture & dependency diagrams + +# Pick the right "in-place" flag for sed (BSD vs GNU) +ifeq ($(shell uname),Darwin) + SED_INPLACE := -i '' +else + SED_INPLACE := -i +endif + +.PHONY: docs +docs: images sbom + @echo "📚 Generating documentation with handsdown…" + uv handsdown --external https://github.com/yourorg/$(PROJECT_NAME)/ \ + -o $(DOCS_DIR)/docs \ + -n app --name "$(PROJECT_NAME)" --cleanup + + @echo "🔧 Rewriting GitHub links…" + @find $(DOCS_DIR)/docs/app -type f \ + -exec sed $(SED_INPLACE) 's#https://github.com/yourorg#https://github.com/ibm/mcp-context-forge#g' {} + + + @sed $(SED_INPLACE) 's#https://github.com/yourorg#https://github.com/ibm/mcp-context-forge#g' \ + $(DOCS_DIR)/docs/README.md + + @cp README.md $(DOCS_DIR)/docs/index.md + @echo "✅ Docs ready in $(DOCS_DIR)/docs" + +.PHONY: images +images: + @echo "🖼️ Generating documentation diagrams…" + @mkdir -p $(DOCS_DIR)/docs/design/images + @code2flow mcpgateway/ --output $(DOCS_DIR)/docs/design/images/code2flow.dot || true + @dot -Tsvg -Gbgcolor=transparent -Gfontname="Arial" -Nfontname="Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle="filled,rounded" -Ecolor=gray -Efontname="Arial" -Efontsize=14 -Efontcolor=black $(DOCS_DIR)/docs/design/images/code2flow.dot -o $(DOCS_DIR)/docs/design/images/code2flow.svg || true + @python3 -m pip install snakefood3 + @python3 -m snakefood3 app > snakefood.dot + @dot -Tpng -Gbgcolor=transparent -Gfontname="Arial" -Nfontname="Arial" -Nfontsize=12 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle="filled,rounded" -Ecolor=gray -Efontname="Arial" -Efontsize=10 -Efontcolor=black snakefood.dot -o $(DOCS_DIR)/docs/design/images/snakefood.png || true + @pyreverse --colorized app || true + @dot -Tsvg -Gbgcolor=transparent -Gfontname="Arial" -Nfontname="Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle="filled,rounded" -Ecolor=gray -Efontname="Arial" -Efontsize=14 -Efontcolor=black packages.dot -o $(DOCS_DIR)/docs/design/images/packages.svg || true + @dot -Tsvg -Gbgcolor=transparent -Gfontname="Arial" -Nfontname="Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle="filled,rounded" -Ecolor=gray -Efontname="Arial" -Efontsize=14 -Efontcolor=black classes.dot -o $(DOCS_DIR)/docs/design/images/classes.svg || true + @rm -f packages.dot classes.dot snakefood.dot || true + +# ============================================================================= +# 🔍 LINTING & STATIC ANALYSIS +# ============================================================================= +# help: 🔍 LINTING & STATIC ANALYSIS +# help: lint - Run the full linting suite (see targets below) +# help: black - Reformat code with black +# help: autoflake - Remove unused imports / variables with autoflake +# help: isort - Organise & sort imports with isort +# help: flake8 - PEP-8 style & logical errors +# help: pylint - Pylint static analysis +# help: mypy - Static type-checking with mypy +# help: bandit - Security scan with bandit +# help: pydocstyle - Docstring style checker +# help: pycodestyle - Simple PEP-8 checker +# help: pre-commit - Run all configured pre-commit hooks +# help: ruff - Ruff linter + formatter +# help: ty - Ty type checker from astral +# help: pyright - Static type-checking with Pyright +# help: radon - Code complexity & maintainability metrics +# help: pyroma - Validate packaging metadata +# help: importchecker - Detect orphaned imports +# help: spellcheck - Spell-check the codebase +# help: fawltydeps - Detect undeclared / unused deps +# help: wily - Maintainability report +# help: pyre - Static analysis with Facebook Pyre +# help: depend - List dependencies in ≈requirements format +# help: snakeviz - Profile & visualise with snakeviz +# help: pstats - Generate PNG call-graph from cProfile stats +# help: spellcheck-sort - Sort local spellcheck dictionary +# help: tox - Run tox across multi-Python versions +# help: sbom - Produce a CycloneDX SBOM and vulnerability scan +# help: pytype - Flow-sensitive type checker +# help: check-manifest - Verify sdist/wheel completeness + +# List of individual lint targets; lint loops over these +LINTERS := isort flake8 pylint mypy bandit pydocstyle pycodestyle pre-commit \ + ruff pyright radon pyroma pyre spellcheck importchecker \ + pytype check-manifest + +.PHONY: lint $(LINTERS) black fawltydeps wily depend snakeviz pstats \ + spellcheck-sort tox \ + pytype check-manifest + +## --------------------------------------------------------------------------- ## +## Master target +## --------------------------------------------------------------------------- ## +lint: + @echo "🔍 Running full lint suite…" + @set -e; for t in $(LINTERS); do \ + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \ + echo "• $$t"; \ + $(MAKE) $$t || true; \ + done + +## --------------------------------------------------------------------------- ## +## Individual targets (alphabetical) +## --------------------------------------------------------------------------- ## +autoflake: ## 🧹 Strip unused imports / vars + autoflake --in-place --remove-all-unused-imports \ + --remove-unused-variables -r mcpgateway + +black: ## 🎨 Reformat code with black + @echo "🎨 black …" && black -l 200 mcpgateway + +isort: ## 🔀 Sort imports + @echo "🔀 isort …" && isort . + +flake8: ## 🐍 flake8 checks + flake8 mcpgateway + +pylint: ## 🐛 pylint checks + pylint mcpgateway + +mypy: ## 🏷️ mypy type-checking + mypy mcpgateway + +bandit: ## 🛡️ bandit security scan + bandit -r mcpgateway + +pydocstyle: ## 📚 Docstring style + pydocstyle mcpgateway + +pycodestyle: ## 📝 Simple PEP-8 checker + pycodestyle mcpgateway --max-line-length=200 + +pre-commit: ## 🪄 Run pre-commit hooks + pre-commit run --all-files --show-diff-on-failure + +ruff: ## ⚡ Ruff lint + format + ruff check mcpgateway && ruff format mcpgateway + +ty: ## ⚡ Ty type checker + ty check mcpgateway + +pyright: ## 🏷️ Pyright type-checking + pyright mcpgateway + +radon: ## 📈 Complexity / MI metrics + radon mi -s mcpgateway && \ + radon cc -s mcpgateway && \ + radon hal mcpgateway && \ + radon raw -s mcpgateway + +pyroma: ## 📦 Packaging metadata check + pyroma -d . + +importchecker: ## 🧐 Orphaned import detector + importchecker . + +spellcheck: ## 🔤 Spell-check + pyspelling || true + +fawltydeps: ## 🏗️ Dependency sanity + fawltydeps --detailed --exclude 'docs/**' . || true + +wily: ## 📈 Maintainability report + @git stash --quiet + @wily build -n 10 . > /dev/null || true + @wily report . || true + @git stash pop --quiet + +pyre: ## 🧠 Facebook Pyre analysis + pyre + +depend: ## 📦 List dependencies + pdm list --freeze + +snakeviz: ## 🐍 Interactive profile visualiser + @python -m cProfile -o mcp.prof app/server.py && snakeviz mcp.prof --server + +pstats: ## 📊 Static call-graph image + @python -m cProfile -o mcp.pstats app/server.py && \ + gprof2dot -w -e 3 -n 3 -s -f pstats mcp.pstats | \ + dot -Tpng -o $(DOCS_DIR)/pstats.png + +spellcheck-sort: .spellcheck-en.txt ## 🔤 Sort spell-list + sort -d -f -o $< $< + +tox: ## 🧪 Multi-Python tox matrix + @echo "🧪 Running tox …" + uv pip install tox-travis tox-pdm + pdm add -G dev + pdm python install 3.11 3.12 + python -m tox -p 2 + +.PHONY: sbom +sbom: + @echo "🛡️ Generating SBOM & security report…" + @rm -Rf "$(VENV_DIR).sbom" + @python3 -m venv "$(VENV_DIR).sbom" + @/bin/bash -c "source $(VENV_DIR).sbom/bin/activate && python3 -m pip install --upgrade pip setuptools pdm uv && python3 -m uv pip install .[dev]" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install cyclonedx-bom sbom2doc" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python -m cyclonedx_py environment --validate '$(VENV_DIR).sbom' --pyproject pyproject.toml --gather-license-texts > $(PROJECT_NAME).sbom.json" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && sbom2doc -i $(PROJECT_NAME).sbom.json -f markdown -o $(DOCS_DIR)/docs/test/sbom.md" + @trivy sbom $(PROJECT_NAME).sbom.json | tee -a $(DOCS_DIR)/docs/test/sbom.md + @/bin/bash -c "source $(VENV_DIR).sbom/bin/activate && python3 -m pdm outdated | tee -a $(DOCS_DIR)/docs/test/sbom.md" + +pytype: + @echo "🧠 Pytype analysis…" + pytype -V 3.12 -j auto mcpgateway + +check-manifest: + @echo "📦 Verifying MANIFEST.in completeness…" + check-manifest + +# ============================================================================= +# 🕸️ WEBPAGE LINTERS & STATIC ANALYSIS +# ============================================================================= +# help: 🕸️ WEBPAGE LINTERS & STATIC ANALYSIS (HTML/CSS/JS lint + security scans + formatting) +# help: install-web-linters - Install HTMLHint, Stylelint, ESLint, Retire.js & Prettier via npm +# help: lint-web - Run HTMLHint, Stylelint, ESLint, Retire.js and npm audit +# help: format-web - Format HTML, CSS & JS files with Prettier +.PHONY: install-web-linters lint-web format-web + +install-web-linters: + @echo "🔧 Installing HTML/CSS/JS lint, security & formatting tools..." + @if [ ! -f package.json ]; then \ + echo "📦 Initializing npm project…"; \ + npm init -y >/dev/null; \ + fi + @npm install --no-save \ + htmlhint \ + stylelint stylelint-config-standard @stylistic/stylelint-config stylelint-order \ + eslint eslint-config-standard \ + retire \ + prettier + +lint-web: install-web-linters + @echo "🔍 Linting HTML files…" + @npx htmlhint "mcpgateway/templates/**/*.html" || true + @echo "🔍 Linting CSS files…" + @npx stylelint "mcpgateway/static/**/*.css" || true + @echo "🔍 Linting JS files…" + @npx eslint "mcpgateway/static/**/*.js" || true + @echo "🔒 Scanning for known JS/CSS library vulnerabilities with retire.js…" + @npx retire --path mcpgateway/static || true + @if [ -f package.json ]; then \ + echo "🔒 Running npm audit (high severity)…"; \ + npm audit --audit-level=high || true; \ + else \ + echo "⚠️ Skipping npm audit: no package.json found"; \ + fi + +format-web: install-web-linters + @echo "🎨 Formatting HTML, CSS & JS with Prettier…" + @npx prettier --write "mcpgateway/templates/**/*.html" \ + "mcpgateway/static/**/*.css" \ + "mcpgateway/static/**/*.js" + + +# ============================================================================= +# 📡 SONARQUBE ANALYSIS (SERVER + SCANNERS) +# ============================================================================= +# help: 📡 SONARQUBE ANALYSIS +# help: sonar-deps-podman - Install podman-compose + supporting tools +# help: sonar-deps-docker - Install docker-compose + supporting tools +# help: sonar-up-podman - Launch SonarQube with podman-compose +# help: sonar-up-docker - Launch SonarQube with docker-compose +# help: sonar-submit-docker - Run containerised Sonar Scanner CLI with Docker +# help: sonar-submit-podman - Run containerised Sonar Scanner CLI with Podman +# help: pysonar-scanner - Run scan with Python wrapper (pysonar-scanner) +# help: sonar-info - How to create a token & which env vars to export + +.PHONY: sonar-deps-podman sonar-deps-docker sonar-up-podman sonar-up-docker \ + sonar-submit-docker sonar-submit-podman pysonar-scanner sonar-info + +# ───── Configuration ───────────────────────────────────────────────────── +# server image tag +SONARQUBE_VERSION ?= latest +SONAR_SCANNER_IMAGE ?= docker.io/sonarsource/sonar-scanner-cli:latest +# service name inside the container. Override for remote SQ +SONAR_HOST_URL ?= http://sonarqube:9000 +# compose network name (podman network ls) +SONAR_NETWORK ?= mcp-context-forge_sonarnet +# analysis props file +SONAR_PROPS ?= sonar-code.properties +# path mounted into scanner: +PROJECT_BASEDIR ?= $(strip $(PWD)) +# Optional auth token: export SONAR_TOKEN=xxxx +# ───────────────────────────────────────────────────────────────────────── + +## ─────────── Dependencies (compose + misc) ───────────────────────────── +sonar-deps-podman: + @echo "🔧 Installing podman-compose …" + python3 -m pip install --quiet podman-compose + +sonar-deps-docker: + @echo "🔧 Ensuring docker-compose is available …" + @which docker-compose >/dev/null || python3 -m pip install --quiet docker-compose + +## ─────────── Run SonarQube server (compose) ──────────────────────────── +sonar-up-podman: + @echo "🚀 Starting SonarQube (v$(SONARQUBE_VERSION)) with podman-compose …" + SONARQUBE_VERSION=$(SONARQUBE_VERSION) \ + podman-compose -f podman-compose-sonarqube.yaml up -d + @sleep 30 && podman ps | grep sonarqube || echo "⚠️ Server may still be starting." + +sonar-up-docker: + @echo "🚀 Starting SonarQube (v$(SONARQUBE_VERSION)) with docker-compose …" + SONARQUBE_VERSION=$(SONARQUBE_VERSION) \ + docker-compose -f podman-compose-sonarqube.yaml up -d + @sleep 30 && docker ps | grep sonarqube || echo "⚠️ Server may still be starting." + +## ─────────── Containerised Scanner CLI (Docker / Podman) ─────────────── +sonar-submit-docker: + @echo "📡 Scanning code with containerised Sonar Scanner CLI (Docker) …" + docker run --rm \ + -e SONAR_HOST_URL="$(SONAR_HOST_URL)" \ + $(if $(SONAR_TOKEN),-e SONAR_TOKEN="$(SONAR_TOKEN)",) \ + -v "$(PROJECT_BASEDIR):/usr/src" \ + $(SONAR_SCANNER_IMAGE) \ + -Dproject.settings=$(SONAR_PROPS) + +sonar-submit-podman: + @echo "📡 Scanning code with containerised Sonar Scanner CLI (Podman) …" + podman run --rm \ + --network $(SONAR_NETWORK) \ + -e SONAR_HOST_URL="$(SONAR_HOST_URL)" \ + $(if $(SONAR_TOKEN),-e SONAR_TOKEN="$(SONAR_TOKEN)",) \ + -v "$(PROJECT_BASEDIR):/usr/src:Z" \ + $(SONAR_SCANNER_IMAGE) \ + -Dproject.settings=$(SONAR_PROPS) + +## ─────────── Python wrapper (pysonar-scanner) ─────────────────────────── +pysonar-scanner: + @echo "🐍 Scanning code with pysonar-scanner (PyPI) …" + @test -f $(SONAR_PROPS) || { echo "❌ $(SONAR_PROPS) not found."; exit 1; } + python3 -m pip install --upgrade --quiet pysonar-scanner + python -m pysonar_scanner \ + -Dproject.settings=$(SONAR_PROPS) \ + -Dsonar.host.url=$(SONAR_HOST_URL) \ + $(if $(SONAR_TOKEN),-Dsonar.login=$(SONAR_TOKEN),) + +## ─────────── Helper: how to create & use the token ────────────────────── +sonar-info: + @echo + @echo "───────────────────────────────────────────────────────────" + @echo "🔑 HOW TO GENERATE A SONAR TOKEN & EXPORT ENV VARS" + @echo "───────────────────────────────────────────────────────────" + @echo "1. Open $(SONAR_HOST_URL) in your browser." + @echo "2. Log in → click your avatar → **My Account → Security**." + @echo "3. Under **Tokens**, enter a name (e.g. mcp-local) and press **Generate**." + @echo "4. **Copy the token NOW** – you will not see it again." + @echo + @echo "Then in your shell:" + @echo " export SONAR_TOKEN=" + @echo " export SONAR_HOST_URL=$(SONAR_HOST_URL)" + @echo + @echo "Now you can run:" + @echo " make sonar-submit-docker # or sonar-submit-podman / pysonar-scanner" + @echo "───────────────────────────────────────────────────────────" + + +# ============================================================================= +# 🛡️ SECURITY & PACKAGE SCANNING +# ============================================================================= +# help: 🛡️ SECURITY & PACKAGE SCANNING +# help: trivy - Scan container image for CVEs (HIGH/CRIT). Needs podman socket enabled +.PHONY: trivy +trivy: + @systemctl --user enable --now podman.socket + @echo "🔎 trivy vulnerability scan…" + @trivy --format table --severity HIGH,CRITICAL image localhost/$(PROJECT_NAME)/$(PROJECT_NAME) + +# help: dockle - Lint the built container image via tarball (no daemon/socket needed) +.PHONY: dockle +DOCKLE_IMAGE ?= $(IMG):latest # mcpgateway/mcpgateway:latest from your build +dockle: + @echo "🔎 dockle scan (tar mode) on $(DOCKLE_IMAGE)…" + @command -v dockle >/dev/null || { \ + echo '❌ Dockle not installed. See https://github.com/goodwithtech/dockle'; exit 1; } + + # Pick docker or podman—whichever is on PATH + @CONTAINER_CLI=$$(command -v docker || command -v podman) ; \ + [ -n "$$CONTAINER_CLI" ] || { echo '❌ docker/podman not found.'; exit 1; }; \ + TARBALL=$$(mktemp /tmp/$(PROJECT_NAME)-dockle-XXXXXX.tar) ; \ + echo "📦 Saving image to $$TARBALL…" ; \ + "$$CONTAINER_CLI" save $(DOCKLE_IMAGE) -o "$$TARBALL" || { rm -f "$$TARBALL"; exit 1; }; \ + echo "🧪 Running Dockle…" ; \ + dockle --no-color --exit-code 1 --exit-level warn --input "$$TARBALL" ; \ + rm -f "$$TARBALL" + +# help: hadolint - Lint Containerfile/Dockerfile(s) with hadolint +.PHONY: hadolint +HADOFILES := Containerfile Dockerfile Dockerfile.* + +# Which files to check (edit as you like) +HADOFILES := Containerfile Containerfile.* Dockerfile Dockerfile.* + +hadolint: + @echo "🔎 hadolint scan…" + + # ─── Ensure hadolint is installed ────────────────────────────────────── + @if ! command -v hadolint >/dev/null 2>&1; then \ + echo "❌ hadolint not found."; \ + case "$$(uname -s)" in \ + Linux*) echo "💡 Install with:"; \ + echo " sudo wget -O /usr/local/bin/hadolint \\"; \ + echo " https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64"; \ + echo " sudo chmod +x /usr/local/bin/hadolint";; \ + Darwin*) echo "💡 Install with Homebrew: brew install hadolint";; \ + *) echo "💡 See other binaries: https://github.com/hadolint/hadolint/releases";; \ + esac; \ + exit 1; \ + fi + + # ─── Run hadolint on each existing file ─────────────────────────────── + @found=0; \ + for f in $(HADOFILES); do \ + if [ -f "$$f" ]; then \ + echo "📝 Scanning $$f"; \ + hadolint "$$f" || true; \ + found=1; \ + fi; \ + done; \ + if [ "$$found" -eq 0 ]; then \ + echo "ℹ️ No Containerfile/Dockerfile found – nothing to scan."; \ + fi + + +# help: pip-audit - Audit Python dependencies for published CVEs +.PHONY: pip-audit +pip-audit: + @echo "🔒 pip-audit vulnerability scan…" + @test -d "$(VENV_DIR)" || $(MAKE) venv + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + python -m pip install --quiet --upgrade pip-audit && \ + pip-audit --progress-spinner ascii --strict || true" + +# ============================================================================= +# 📦 DEPENDENCY MANAGEMENT +# ============================================================================= +# help: 📦 DEPENDENCY MANAGEMENT +# help: deps-update - Run update-deps.py to update all dependencies in pyproject.toml and docs/requirements.txt +# help: containerfile-update - Update base image in Containerfile to latest tag + +.PHONY: deps-update containerfile-update + +deps-update: + @echo "⬆️ Updating project dependencies via update-deps.py…" + @test -f update-deps.py || { echo "❌ update-deps.py not found in root directory."; exit 1; } + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python update-deps.py" + @echo "✅ Dependencies updated in pyproject.toml and docs/requirements.txt" + +containerfile-update: + @echo "⬆️ Updating base image in Containerfile to :latest tag…" + @test -f Containerfile || { echo "❌ Containerfile not found."; exit 1; } + @sed -i.bak -E 's|^(FROM\s+\S+):[^\s]+|\1:latest|' Containerfile && rm -f Containerfile.bak + @echo "✅ Base image updated to latest." + +# ============================================================================= +# 🦭 PODMAN CONTAINER BUILD & RUN +# ============================================================================= +# help: 🦭 PODMAN CONTAINER BUILD & RUN +# help: podman-dev - Build development container image +# help: podman - Build production container image +# help: podman-run - Run the container on HTTP (port 4444) +# help: podman-run-shell - Run the container on HTTP (port 4444) and start a shell +# help: podman-run-ssl - Run the container on HTTPS (port 4444, self-signed) +# help: podman-stop - Stop & remove the container +# help: podman-test - Quick curl smoke-test against the container +# help: podman-logs - Follow container logs (⌃C to quit) + +.PHONY: podman-dev podman podman-run podman-run-shell podman-run-ssl podman-stop podman-test + +IMG ?= $(PROJECT_NAME)/$(PROJECT_NAME) +IMG_DEV = $(IMG)-dev +IMG_PROD = $(IMG) + +podman-dev: + @echo "🦭 Building dev container…" + podman build --ssh default --platform=linux/amd64 --squash \ + -t $(IMG_DEV) . + +podman: + @echo "🦭 Building container using ubi9-minimal…" + podman build --ssh default --platform=linux/amd64 --squash \ + -t $(IMG_PROD) . + podman images $(IMG_PROD) + +podman-prod: + @echo "🦭 Building production container from Containerfile.lite (ubi-micro → scratch)…" + podman build --ssh default \ + --platform=linux/amd64 \ + --squash \ + -f Containerfile.lite \ + -t $(IMG_PROD) \ + . + podman images $(IMG_PROD) + +## -------------------- R U N (HTTP) --------------------------------------- +podman-run: + @echo "🚀 Starting podman container (HTTP)…" + -podman stop $(PROJECT_NAME) 2>/dev/null || true + -podman rm $(PROJECT_NAME) 2>/dev/null || true + podman run --name $(PROJECT_NAME) \ + --env-file=.env \ + -p 4444:4444 \ + --restart=always --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + --health-cmd="curl --fail http://localhost:4444/health || exit 1" \ + --health-interval=1m --health-retries=3 \ + --health-start-period=30s --health-timeout=10s \ + -d $(IMG_PROD) + @sleep 2 && podman logs $(PROJECT_NAME) | tail -n +1 + +podman-run-shell: + @echo "🚀 Starting podman container shell…" + podman run --name $(PROJECT_NAME)-shell \ + --env-file=.env \ + -p 4444:4444 \ + --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + -it --rm $(IMG_PROD) \ + sh -c 'env; exec sh' + +## -------------------- R U N (HTTPS) -------------------------------------- +podman-run-ssl: certs + @echo "🚀 Starting podman container (TLS)…" + -podman stop $(PROJECT_NAME) 2>/dev/null || true + -podman rm $(PROJECT_NAME) 2>/dev/null || true + podman run --name $(PROJECT_NAME) \ + --env-file=.env \ + -e SSL=true \ + -e CERT_FILE=certs/cert.pem \ + -e KEY_FILE=certs/key.pem \ + -v $(PWD)/certs:/app/certs:ro,Z \ + -p 4444:4444 \ + --restart=always --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + --health-cmd="curl -k --fail https://localhost:4444/health || exit 1" \ + --health-interval=1m --health-retries=3 \ + --health-start-period=30s --health-timeout=10s \ + -d $(IMG_PROD) + @sleep 2 && podman logs $(PROJECT_NAME) | tail -n +1 + +podman-stop: + @echo "🛑 Stopping podman container…" + -podman stop $(PROJECT_NAME) && podman rm $(PROJECT_NAME) || true + +podman-test: + @echo "🔬 Testing podman endpoint…" + @echo "• HTTP -> curl http://localhost:4444/system/test" + @echo "• HTTPS -> curl -k https://localhost:4444/system/test" + +podman-logs: + @echo "📜 Streaming podman logs (press Ctrl+C to exit)…" + @podman logs -f $(PROJECT_NAME) + +# help: podman-stats - Show container resource stats (if supported) +.PHONY: podman-stats +podman-stats: + @echo "📊 Showing Podman container stats…" + @if podman info --format '{{.Host.CgroupManager}}' | grep -q 'cgroupfs'; then \ + echo "⚠️ podman stats not supported in rootless mode without cgroups v2 (e.g., WSL2)"; \ + echo "👉 Falling back to 'podman top'"; \ + podman top $(PROJECT_NAME); \ + else \ + podman stats --no-stream; \ + fi + +# help: podman-top - Show live top-level process info in container +.PHONY: podman-top +podman-top: + @echo "🧠 Showing top-level processes in the Podman container…" + podman top $(PROJECT_NAME) + +# help: podman-shell - Open an interactive shell inside the Podman container +.PHONY: podman-shell +podman-shell: + @echo "🔧 Opening shell in Podman container…" + @podman exec -it $(PROJECT_NAME) bash || podman exec -it $(PROJECT_NAME) /bin/sh + +# ============================================================================= +# 🐋 DOCKER BUILD & RUN +# ============================================================================= +# help: 🐋 DOCKER BUILD & RUN +# help: docker-dev - Build development Docker image +# help: docker - Build production Docker image +# help: docker-run - Run the container on HTTP (port 4444) +# help: docker-run-ssl - Run the container on HTTPS (port 4444, self-signed) +# help: docker-stop - Stop & remove the container +# help: docker-test - Quick curl smoke-test against the container +# help: docker-logs - Follow container logs (⌃C to quit) + +.PHONY: docker-dev docker docker-run docker-run-ssl docker-stop docker-test + +IMG_DOCKER_DEV = $(IMG)-dev:latest +IMG_DOCKER_PROD = $(IMG):latest + +docker-dev: + @echo "🐋 Building dev Docker image…" + docker build --platform=linux/amd64 -t $(IMG_DOCKER_DEV) . + +docker: + @echo "🐋 Building production Docker image…" + docker build --platform=linux/amd64 -t $(IMG_DOCKER_PROD) -f Containerfile . + +## -------------------- R U N (HTTP) --------------------------------------- +docker-run: + @echo "🚀 Starting Docker container (HTTP)…" + -docker stop $(PROJECT_NAME) 2>/dev/null || true + -docker rm $(PROJECT_NAME) 2>/dev/null || true + docker run --name $(PROJECT_NAME) \ + --env-file=.env \ + -p 4444:4444 \ + --restart=always --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + --health-cmd="curl --fail http://localhost:4444/health || exit 1" \ + --health-interval=1m --health-retries=3 \ + --health-start-period=30s --health-timeout=10s \ + -d $(IMG_DOCKER_PROD) + @sleep 2 && docker logs $(PROJECT_NAME) | tail -n +1 + +## -------------------- R U N (HTTPS) -------------------------------------- +docker-run-ssl: certs + @echo "🚀 Starting Docker container (TLS)…" + -docker stop $(PROJECT_NAME) 2>/dev/null || true + -docker rm $(PROJECT_NAME) 2>/dev/null || true + docker run --name $(PROJECT_NAME) \ + --env-file=.env \ + -e SSL=true \ + -e CERT_FILE=certs/cert.pem \ + -e KEY_FILE=certs/key.pem \ + -v $(PWD)/certs:/app/certs:ro \ + -p 4444:4444 \ + --restart=always --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + --health-cmd="curl -k --fail https://localhost:4444/health || exit 1" \ + --health-interval=1m --health-retries=3 \ + --health-start-period=30s --health-timeout=10s \ + -d $(IMG_DOCKER_PROD) + @sleep 2 && docker logs $(PROJECT_NAME) | tail -n +1 + +docker-stop: + @echo "🛑 Stopping Docker container…" + -docker stop $(PROJECT_NAME) && docker rm $(PROJECT_NAME) || true + +docker-test: + @echo "🔬 Testing Docker endpoint…" + @echo "• HTTP -> curl http://localhost:4444/system/test" + @echo "• HTTPS -> curl -k https://localhost:4444/system/test" + + +docker-logs: + @echo "📜 Streaming Docker logs (press Ctrl+C to exit)…" + @docker logs -f $(PROJECT_NAME) + +# help: docker-stats - Show container resource usage stats (non-streaming) +.PHONY: docker-stats +docker-stats: + @echo "📊 Showing Docker container stats…" + @docker stats --no-stream || { echo "⚠️ Failed to fetch docker stats. Falling back to 'docker top'…"; docker top $(PROJECT_NAME); } + +# help: docker-top - Show top-level process info in Docker container +.PHONY: docker-top +docker-top: + @echo "🧠 Showing top-level processes in the Docker container…" + docker top $(PROJECT_NAME) + +# help: docker-shell - Open an interactive shell inside the Docker container +.PHONY: docker-shell +docker-shell: + @echo "🔧 Opening shell in Docker container…" + @docker exec -it $(PROJECT_NAME) bash || docker exec -it $(PROJECT_NAME) /bin/sh + +# ============================================================================= +# ☁️ IBM CLOUD CODE ENGINE +# ============================================================================= +# help: ☁️ IBM CLOUD CODE ENGINE +# help: ibmcloud-check-env - Verify all required IBM Cloud env vars are set +# help: ibmcloud-cli-install - Auto-install IBM Cloud CLI + required plugins (OS auto-detected) +# help: ibmcloud-login - Login to IBM Cloud CLI using IBMCLOUD_API_KEY (--sso) +# help: ibmcloud-ce-login - Set Code Engine target project and region +# help: ibmcloud-list-containers - List deployed Code Engine apps +# help: ibmcloud-tag - Tag container image for IBM Container Registry +# help: ibmcloud-push - Push image to IBM Container Registry +# help: ibmcloud-deploy - Deploy (or update) container image in Code Engine +# help: ibmcloud-ce-logs - Stream logs for the deployed application +# help: ibmcloud-ce-status - Get deployment status +# help: ibmcloud-ce-rm - Delete the Code Engine application + +.PHONY: ibmcloud-check-env ibmcloud-cli-install ibmcloud-login ibmcloud-ce-login \ + ibmcloud-list-containers ibmcloud-tag ibmcloud-push ibmcloud-deploy \ + ibmcloud-ce-logs ibmcloud-ce-status ibmcloud-ce-rm + +# ───────────────────────────────────────────────────────────────────────────── +# 📦 Load environment file with IBM Cloud Code Engine configuration +# • .env.ce – IBM Cloud / Code Engine deployment vars +# ───────────────────────────────────────────────────────────────────────────── +-include .env.ce + +# Export only the IBM-specific variables (those starting with IBMCLOUD_) +export $(shell grep -E '^IBMCLOUD_' .env.ce 2>/dev/null | sed -E 's/^\s*([^=]+)=.*/\1/') + +## Optional / defaulted ENV variables: +IBMCLOUD_CPU ?= 1 # vCPU allocation for Code Engine app +IBMCLOUD_MEMORY ?= 4G # Memory allocation for Code Engine app +IBMCLOUD_REGISTRY_SECRET ?= $(IBMCLOUD_PROJECT)-registry-secret + +## Required ENV variables: +# IBMCLOUD_REGION = IBM Cloud region (e.g. us-south) +# IBMCLOUD_PROJECT = Code Engine project name +# IBMCLOUD_RESOURCE_GROUP = IBM Cloud resource group name (e.g. default) +# IBMCLOUD_CODE_ENGINE_APP = Code Engine app name +# IBMCLOUD_IMAGE_NAME = Full image path (e.g. us.icr.io/namespace/app:tag) +# IBMCLOUD_IMG_PROD = Local container image name +# IBMCLOUD_API_KEY = IBM Cloud IAM API key (optional, use --sso if not set) + +ibmcloud-check-env: + @bash -eu -o pipefail -c '\ + echo "🔍 Verifying required IBM Cloud variables (.env.ce)…"; \ + missing=0; \ + for var in IBMCLOUD_REGION IBMCLOUD_PROJECT IBMCLOUD_RESOURCE_GROUP \ + IBMCLOUD_CODE_ENGINE_APP IBMCLOUD_IMAGE_NAME IBMCLOUD_IMG_PROD \ + IBMCLOUD_CPU IBMCLOUD_MEMORY IBMCLOUD_REGISTRY_SECRET; do \ + if [ -z "$${!var}" ]; then \ + echo "❌ Missing: $$var"; \ + missing=1; \ + fi; \ + done; \ + if [ -z "$$IBMCLOUD_API_KEY" ]; then \ + echo "⚠️ IBMCLOUD_API_KEY not set – interactive SSO login will be used"; \ + else \ + echo "🔑 IBMCLOUD_API_KEY found"; \ + fi; \ + if [ "$$missing" -eq 0 ]; then \ + echo "✅ All required variables present in .env.ce"; \ + else \ + echo "💡 Add the missing keys to .env.ce before continuing."; \ + exit 1; \ + fi' + +ibmcloud-cli-install: + @echo "☁️ Detecting OS and installing IBM Cloud CLI…" + @if grep -qi microsoft /proc/version 2>/dev/null; then \ + echo "🔧 Detected WSL2"; \ + curl -fsSL https://clis.cloud.ibm.com/install/linux | sh; \ + elif [ "$$(uname)" = "Darwin" ]; then \ + echo "🍏 Detected macOS"; \ + curl -fsSL https://clis.cloud.ibm.com/install/osx | sh; \ + elif [ "$$(uname)" = "Linux" ]; then \ + echo "🐧 Detected Linux"; \ + curl -fsSL https://clis.cloud.ibm.com/install/linux | sh; \ + elif command -v powershell.exe >/dev/null; then \ + echo "🪟 Detected Windows"; \ + powershell.exe -Command "iex (New-Object Net.WebClient).DownloadString('https://clis.cloud.ibm.com/install/powershell')"; \ + else \ + echo "❌ Unsupported OS"; exit 1; \ + fi + @echo "✅ CLI installed. Installing required plugins…" + @ibmcloud plugin install container-registry -f + @ibmcloud plugin install code-engine -f + @ibmcloud --version + +ibmcloud-login: + @echo "🔐 Starting IBM Cloud login…" + @echo "──────────────────────────────────────────────" + @echo "👤 User: $(USER)" + @echo "📍 Region: $(IBMCLOUD_REGION)" + @echo "🧵 Resource Group: $(IBMCLOUD_RESOURCE_GROUP)" + @if [ -n "$(IBMCLOUD_API_KEY)" ]; then \ + echo "🔑 Auth Mode: API Key (with --sso)"; \ + else \ + echo "🔑 Auth Mode: Interactive (--sso)"; \ + fi + @echo "──────────────────────────────────────────────" + @if [ -z "$(IBMCLOUD_REGION)" ] || [ -z "$(IBMCLOUD_RESOURCE_GROUP)" ]; then \ + echo "❌ IBMCLOUD_REGION or IBMCLOUD_RESOURCE_GROUP is missing. Aborting."; \ + exit 1; \ + fi + @if [ -n "$(IBMCLOUD_API_KEY)" ]; then \ + ibmcloud login --apikey "$(IBMCLOUD_API_KEY)" --sso -r "$(IBMCLOUD_REGION)" -g "$(IBMCLOUD_RESOURCE_GROUP)"; \ + else \ + ibmcloud login --sso -r "$(IBMCLOUD_REGION)" -g "$(IBMCLOUD_RESOURCE_GROUP)"; \ + fi + @echo "🎯 Targeting region and resource group…" + @ibmcloud target -r "$(IBMCLOUD_REGION)" -g "$(IBMCLOUD_RESOURCE_GROUP)" + @ibmcloud target + +ibmcloud-ce-login: + @echo "🎯 Targeting Code Engine project '$(IBMCLOUD_PROJECT)' in region '$(IBMCLOUD_REGION)'…" + @ibmcloud ce project select --name "$(IBMCLOUD_PROJECT)" + +ibmcloud-list-containers: + @echo "📦 Listing Code Engine images" + ibmcloud cr images + @echo "📦 Listing Code Engine applications…" + @ibmcloud ce application list + +ibmcloud-tag: + @echo "🏷️ Tagging image $(IBMCLOUD_IMG_PROD) → $(IBMCLOUD_IMAGE_NAME)" + podman tag $(IBMCLOUD_IMG_PROD) $(IBMCLOUD_IMAGE_NAME) + podman images | head -3 + +ibmcloud-push: + @echo "📤 Logging into IBM Container Registry and pushing image…" + @ibmcloud cr login + podman push $(IBMCLOUD_IMAGE_NAME) + +ibmcloud-deploy: + @echo "🚀 Deploying image to Code Engine as '$(IBMCLOUD_CODE_ENGINE_APP)' using registry secret $(IBMCLOUD_REGISTRY_SECRET)…" + @if ibmcloud ce application get --name $(IBMCLOUD_CODE_ENGINE_APP) > /dev/null 2>&1; then \ + echo "🔁 Updating existing app…"; \ + ibmcloud ce application update --name $(IBMCLOUD_CODE_ENGINE_APP) \ + --image $(IBMCLOUD_IMAGE_NAME) \ + --cpu $(IBMCLOUD_CPU) --memory $(IBMCLOUD_MEMORY) \ + --registry-secret $(IBMCLOUD_REGISTRY_SECRET); \ + else \ + echo "🆕 Creating new app…"; \ + ibmcloud ce application create --name $(IBMCLOUD_CODE_ENGINE_APP) \ + --image $(IBMCLOUD_IMAGE_NAME) \ + --cpu $(IBMCLOUD_CPU) --memory $(IBMCLOUD_MEMORY) \ + --port 4444 \ + --registry-secret $(IBMCLOUD_REGISTRY_SECRET); \ + fi + +ibmcloud-ce-logs: + @echo "📜 Streaming logs for '$(IBMCLOUD_CODE_ENGINE_APP)'…" + @ibmcloud ce application logs --name $(IBMCLOUD_CODE_ENGINE_APP) --follow + +ibmcloud-ce-status: + @echo "📈 Application status for '$(IBMCLOUD_CODE_ENGINE_APP)'…" + @ibmcloud ce application get --name $(IBMCLOUD_CODE_ENGINE_APP) + +ibmcloud-ce-rm: + @echo "🗑️ Deleting Code Engine app: $(IBMCLOUD_CODE_ENGINE_APP)…" + @ibmcloud ce application delete --name $(IBMCLOUD_CODE_ENGINE_APP) -f diff --git a/README.md b/README.md new file mode 100644 index 0000000000..9d63d7806a --- /dev/null +++ b/README.md @@ -0,0 +1,971 @@ +# MCP Gateway + +A flexible feature-rich FastAPI-based gateway for the Model Context Protocol (MCP) that unifies and federates tools, resources, prompts, servers and peer gateways, wraps any REST API as MCP-compliant tools or virtual servers, and exposes everything over HTTP/JSON-RPC, WebSocket, Server-Sent Events (SSE) and stdio transports—all manageable via a rich, interactive Admin UI and packaged as a container with support for any SQLAlchemy supported database. + +![MCP Gateway](docs/docs/images/mcpgateway.gif) +--- + +## Overview & Goals + +MCP Gateway builds on the MCP spec by sitting **in front of** MCP Server or REST API to: + +* **Act as a true gateway**, centralizing tool, resource and prompt registries while preserving the official MCP 2025-03-26 protocol +* **Federate** multiple MCP servers into one unified endpoint—auto-discover peers (mDNS or explicit), health-check them, and merge their capabilities +* **Virtualize** non-MCP services as “virtual servers” so you can register any REST API or function endpoint and expose it under MCP semantics +* **Adapt** arbitrary REST/HTTP APIs into MCP tools with JSON-Schema input validation, retry/rate-limit policies and transparent JSON-RPC invocation +* **Simplify** deployments with a full admin UI, rich transports, pre-built DX pipelines and production-grade observability + +--- + +## Features + +### Core + +* **Full MCP 2025-03-26**: initialize, ping, notify, completion, sampling (SSE), plus JSON-RPC fallback +* **Gateway Layer**: sits alongside or in front of MCP servers, enforcing MCP rules and consolidating multiple backends +* **Multi-Transport**: HTTP/JSON-RPC, WebSocket (ping/pong), SSE (one-way + backchannel), stdio +* **Federation**: + + * Auto-discover or configure peer gateways + * Periodic health checks with fail-over + * Transparent merging of remote registries into one catalog +* **Virtual Servers**: wrap any non-MCP endpoint (REST, gRPC, function) as a managed MCP server with minimal config +* **REST-to-MCP Adapter**: register any REST API as an MCP tool—automatic schema extraction, auth headers, retry/rate limits +* **Resources**: templated URIs, LRU+TTL caching, MIME detection, real-time SSE subscriptions +* **Prompts**: Jinja2 templates, JSON-Schema enforcement, multimodal blocks, versioning & rollback +* **Tools**: MCP-native or REST-based; input validation, retry logic, rate-limit/concurrency controls + +### Extras + +* **Admin UI** (HTMX + Alpine.js + Tailwind): full CRUD for servers, tools, resources, prompts, gateways, roots & metrics +* **Authentication & Authorization**: Basic, JWT Bearer, custom header schemes, per-endpoint DI +* **Persistence & Migrations**: async SQLAlchemy ORM (SQLite, Postgres, MySQL, etc.), Alembic auto-migrations +* **Event System**: uniform event envelopes on WS/SSE fan-out, server-side filters, backchannel hooks +* **Observability & Health**: structured JSON logs, `/health` latency metrics decorator on every handler +* **Developer Experience**: Makefile targets, pre-commit (`ruff`, `black`, `mypy`, `isort`), live-reload, 400+ tests, CI badges + +--- + +## Quick Start + +### Prerequisites + +* **Python ≥ 3.10** +* **GNU Make** (all common workflows are Make targets) +* Optional: **Docker / Podman** for containerised runs + +### One-liner (dev) + +```bash +make venv install serve +```` + +What it does: + +1. Creates / activates a `.venv` in the project root +2. Installs the gateway and necessary dependencies +3. Launches **Gunicorn** (Uvicorn workers) on [http://localhost:4444](http://localhost:4444) + +For development, you can use: + +```bash +make install-dev # Install development dependencies, ex: linters and test harness +make lint # optional: run style checks (ruff, mypy, etc.) +``` + +### Containerised (self-signed TLS) + +```bash +make podman # build production image +make podman-run-ssl # run at https://localhost:4444 +``` + +### Generate an admin JWT + +```bash +python -m mcpgateway.utils.create_jwt_token \ + -u admin \ + -e 10080 | tee token.txt # 7 days (minutes) + +export MCPGATEWAY_BEARER_TOKEN=$(cat token.txt) +``` + +### Smoke-test the API + +```bash +curl -k -sX GET \ + -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + https://localhost:4444/tools | jq +``` + +You should receive `[]` until you register a tool. + +--- + +## Installation + +### Via Make + +```bash +make venv install # create .venv + install deps +make serve # gunicorn on :4444 +``` + +### UV (alternative) + +```bash +uv venv && source .venv/bin/activate +uv pip install -e .[dev] +``` + +### pip (alternative) + +```bash +python -m venv .venv && source .venv/bin/activate +pip install -e ".[dev]" +``` + +### Optional (PostgreSQL adapter) + +You can configure the gateway with SQLite, PostgreSQL (or any other compatible database) in .env. + +When using PostgreSQL, you need to install `psycopg2` driver. + +```bash +uv pip install psycopg2-binary # dev convenience +# or +uv pip install psycopg2 # production build +``` + +#### Quick Postgres container + +```bash +docker run --name mcp-postgres \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=mysecretpassword \ + -e POSTGRES_DB=mcp \ + -p 5432:5432 -d postgres +``` + +--- + +## Configuration (`.env` or env vars) + +> ⚠️ If any required `.env` variable is missing or invalid, the gateway will fail fast at startup with a validation error via Pydantic. + +You can get started by copying the provided `.env.examples` to `.env` and making the necessary edits to fit your environment. + +### Basic + +| Setting | Description | Default | Options | +| --------------- | ---------------------------------------- | ---------------------- | ---------------------- | +| `APP_NAME` | Gateway / OpenAPI title | `MCP Gateway` | string | +| `HOST` | Bind address for the app | `0.0.0.0` | IPv4/IPv6 | +| `PORT` | Port the server listens on | `4444` | 1–65535 | +| `DATABASE_URL` | SQLAlchemy connection URL | `sqlite:///./mcp.db` | any SQLAlchemy dialect | +| `APP_ROOT_PATH` | Subpath prefix for app (e.g. `/gateway`) | (empty) | string | +| `TEMPLATES_DIR` | Path to Jinja2 templates | `mcpgateway/templates` | path | +| `STATIC_DIR` | Path to static files | `mcpgateway/static` | path | + +> 💡 Use `APP_ROOT_PATH=/foo` if reverse-proxying under a subpath like `https://host.com/foo/`. + +### Authentication + +| Setting | Description | Default | Options | +| --------------------- | ---------------------------------------------------------------- | ------------- | ---------- | +| `BASIC_AUTH_USER` | Username for Admin UI login and HTTP Basic authentication | `admin` | string | +| `BASIC_AUTH_PASSWORD` | Password for Admin UI login and HTTP Basic authentication | `changeme` | string | +| `AUTH_REQUIRED` | Require authentication for all API routes | `true` | bool | +| `JWT_SECRET_KEY` | Secret key used to **sign JWT tokens** for API access | `my-test-key` | string | +| `JWT_ALGORITHM` | Algorithm used to sign the JWTs (`HS256` is default, HMAC-based) | `HS256` | PyJWT algs | +| `TOKEN_EXPIRY` | Expiry of generated JWTs in minutes | `10080` | int > 0 | +| `AUTH_ENCRYPTION_SECRET` | Passphrase used to derive AES key for encrypting tool auth headers | `my-test-salt` | string | + +> 🔐 `BASIC_AUTH_USER`/`PASSWORD` are used for: +> +> * Logging into the web-based Admin UI +> * Accessing APIs via Basic Auth (`curl -u admin:changeme`) +> +> 🔑 `JWT_SECRET_KEY` is used to: +> +> * Sign JSON Web Tokens (`Authorization: Bearer `) +> * Generate tokens via: +> +> ```bash +> python -m mcpgateway.utils.create_jwt_token -u admin -e 10080 > token.txt +> export MCPGATEWAY_BEARER_TOKEN=$(cat token.txt) +> ``` +> * Tokens allow non-interactive API clients to authenticate securely. +> +> 🧪 Set `AUTH_REQUIRED=false` during development if you want to disable all authentication (e.g. for local testing or open APIs) or clients that don't support SSE authentication. +> In production, you should use the SSE to stdio `mcpgateway-wrapper` for such tools that don't support authenticated SSE, while still ensuring the gateway uses authentication. +> +> 🔐 `AUTH_ENCRYPTION_SECRET` is used to encrypt and decrypt tool authentication credentials (`auth_value`). +> You must set the same value across environments to decode previously stored encrypted auth values. +> Recommended: use a long, random string. + +### UI Features + +| Setting | Description | Default | Options | +| ------------------------------ | -------------------------------------- | ------- | ------- | +| `MCPGATEWAY_UI_ENABLED` | Enable the interactive Admin dashboard | `true` | bool | +| `MCPGATEWAY_ADMIN_API_ENABLED` | Enable API endpoints for admin ops | `true` | bool | + +> 🖥️ Set both to `false` to disable management UI and APIs in production. + +### Security + +| Setting | Description | Default | Options | +| ----------------- | ------------------------------ | ---------------------------------------------- | ---------- | +| `SKIP_SSL_VERIFY` | Skip upstream TLS verification | `false` | bool | +| `ALLOWED_ORIGINS` | CORS allow‐list | `["http://localhost","http://localhost:4444"]` | JSON array | +| `CORS_ENABLED` | Enable CORS | `true` | bool | + +> Note: do not quote the ALLOWED_ORIGINS values, this needs to be valid JSON, such as: `ALLOWED_ORIGINS=["http://localhost", "http://localhost:4444"]` + +### Logging + +| Setting | Description | Default | Options | +| ------------ | ----------------- | ------- | ------------------ | +| `LOG_LEVEL` | Minimum log level | `INFO` | `DEBUG`…`CRITICAL` | +| `LOG_FORMAT` | Log format | `json` | `json`, `text` | +| `LOG_FILE` | Log output file | (none) | path or empty | + +### Transport + +| Setting | Description | Default | Options | +| ------------------------- | ---------------------- | ------- | ------------------------------- | +| `TRANSPORT_TYPE` | Enabled transports | `all` | `http`,`ws`,`sse`,`stdio`,`all` | +| `WEBSOCKET_PING_INTERVAL` | WebSocket ping (secs) | `30` | int > 0 | +| `SSE_RETRY_TIMEOUT` | SSE retry timeout (ms) | `5000` | int > 0 | + +### Federation + +| Setting | Description | Default | Options | +| -------------------------- | ---------------------- | ------- | ---------- | +| `FEDERATION_ENABLED` | Enable federation | `true` | bool | +| `FEDERATION_DISCOVERY` | Auto‐discover peers | `false` | bool | +| `FEDERATION_PEERS` | Comma-sep peer URLs | `[]` | JSON array | +| `FEDERATION_TIMEOUT` | Gateway timeout (secs) | `30` | int > 0 | +| `FEDERATION_SYNC_INTERVAL` | Sync interval (secs) | `300` | int > 0 | + +### Resources + +| Setting | Description | Default | Options | +| --------------------- | --------------------- | ---------- | ---------- | +| `RESOURCE_CACHE_SIZE` | LRU cache size | `1000` | int > 0 | +| `RESOURCE_CACHE_TTL` | Cache TTL (seconds) | `3600` | int > 0 | +| `MAX_RESOURCE_SIZE` | Max resource bytes | `10485760` | int > 0 | +| `ALLOWED_MIME_TYPES` | Acceptable MIME types | see code | JSON array | + +### Tools + +| Setting | Description | Default | Options | +| ----------------------- | ------------------------------ | ------- | ------- | +| `TOOL_TIMEOUT` | Tool invocation timeout (secs) | `60` | int > 0 | +| `MAX_TOOL_RETRIES` | Max retry attempts | `3` | int ≥ 0 | +| `TOOL_RATE_LIMIT` | Tool calls per minute | `100` | int > 0 | +| `TOOL_CONCURRENT_LIMIT` | Concurrent tool invocations | `10` | int > 0 | + +### Prompts + +| Setting | Description | Default | Options | +| ----------------------- | -------------------------------- | -------- | ------- | +| `PROMPT_CACHE_SIZE` | Cached prompt templates | `100` | int > 0 | +| `MAX_PROMPT_SIZE` | Max prompt template size (bytes) | `102400` | int > 0 | +| `PROMPT_RENDER_TIMEOUT` | Jinja render timeout (secs) | `10` | int > 0 | + +### Health Checks + +| Setting | Description | Default | Options | +| ----------------------- | ----------------------------- | ------- | ------- | +| `HEALTH_CHECK_INTERVAL` | Health poll interval (secs) | `60` | int > 0 | +| `HEALTH_CHECK_TIMEOUT` | Health request timeout (secs) | `10` | int > 0 | +| `UNHEALTHY_THRESHOLD` | Fail-count before unhealthy | `3` | int > 0 | + +### Database + +| Setting | Description | Default | Options | +| ----------------- | ------------------------------- | ------- | ------- | +| `DB_POOL_SIZE` | SQLAlchemy connection pool size | `200` | int > 0 | +| `DB_MAX_OVERFLOW` | Extra connections beyond pool | `10` | int ≥ 0 | +| `DB_POOL_TIMEOUT` | Wait for connection (secs) | `30` | int > 0 | +| `DB_POOL_RECYCLE` | Recycle connections (secs) | `3600` | int > 0 | + +### Cache Backend + +| Setting | Description | Default | Options | +| -------------- | -------------------------- | -------- | ------------------------ | +| `CACHE_TYPE` | Backend (`memory`/`redis`) | `memory` | `none`, `memory`,`redis` | +| `REDIS_URL` | Redis connection URL | (none) | string or empty | +| `CACHE_PREFIX` | Key prefix | `mcpgw:` | string | + +> 🧠 `none` disables caching entirely. Use `memory` for dev, `database` for persistence, or `redis` for distributed caching. + +### Development + +| Setting | Description | Default | Options | +| ---------- | ---------------------- | ------- | ------- | +| `DEV_MODE` | Enable dev mode | `false` | bool | +| `RELOAD` | Auto-reload on changes | `false` | bool | +| `DEBUG` | Debug logging | `false` | bool | + +--- + +## Running + +### Makefile + +```bash + make serve # Run production Gunicorn server on + make serve-ssl # Run Gunicorn behind HTTPS on :4444 (uses ./certs) +``` + +### Script helper + +To run the development (uvicorn) server: + +```bash +make dev +# or +./run.sh --reload --log debug --workers 2 +``` + +> `run.sh` is a wrapper around `uvicorn` that loads `.env`, supports reload, and passes arguments to the server. + +Key flags: + +| Flag | Purpose | Example | +| ---------------- | ---------------- | ------------------ | +| `-e, --env FILE` | load env-file | `--env prod.env` | +| `-H, --host` | bind address | `--host 127.0.0.1` | +| `-p, --port` | listen port | `--port 8080` | +| `-w, --workers` | gunicorn workers | `--workers 4` | +| `-r, --reload` | auto-reload | `--reload` | + +### Manual (Uvicorn) + +```bash +uvicorn mcpgateway.main:app --host 0.0.0.0 --port 4444 --workers 4 +``` + +--- + +## Authentication examples + +```bash +# Generate a JWT token using JWT_SECRET_KEY and export it as MCPGATEWAY_BEARER_TOKEN +export MCPGATEWAY_BEARER_TOKEN=$(JWT_SECRET_KEY=my-test-key python3 mcpgateway/utils/create_jwt_token.py) + +# Use the JWT token in an API call +curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools +``` + +--- + +## ☁️ AAWS / Azure / OpenShift + +Deployment details can be found in the GitHub Pages. + +## ☁️ IBM Cloud Code Engine Deployment + +This project supports deployment to [IBM Cloud Code Engine](https://cloud.ibm.com/codeengine) using the **ibmcloud** CLI and the IBM Container Registry. + +--- + +### 🔧 Prerequisites + +- Podman **or** Docker installed locally +- IBM Cloud CLI (use `make ibmcloud-cli-install` to install) +- An [IBM Cloud API key](https://cloud.ibm.com/iam/apikeys) with access to Code Engine & Container Registry +- Code Engine and Container Registry services **enabled** in your IBM Cloud account + +--- + +### 📦 Environment Variables + +Create a **`.env`** file (or export the variables in your shell). +The first block is **required**; the second provides **tunable defaults** you can override: + +```bash +# ── Required ───────────────────────────────────────────── +IBMCLOUD_REGION=us-south +IBMCLOUD_RESOURCE_GROUP=default +IBMCLOUD_PROJECT=my-codeengine-project +IBMCLOUD_CODE_ENGINE_APP=mcpgateway +IBMCLOUD_IMAGE_NAME=us.icr.io/myspace/mcpgateway:latest +IBMCLOUD_IMG_PROD=mcpgateway/mcpgateway +IBMCLOUD_API_KEY=your_api_key_here # Optional – omit to use interactive `ibmcloud login --sso` + +# ── Optional overrides (sensible defaults provided) ────── +IBMCLOUD_CPU=1 # vCPUs for the app +IBMCLOUD_MEMORY=4G # Memory allocation +IBMCLOUD_REGISTRY_SECRET=my-regcred # Name of the Container Registry secret +```` + +> ✅ **Quick check:** `make ibmcloud-check-env` + +--- + +### 🚀 Make Targets + +| Target | Purpose | +| --------------------------- | ------------------------------------------------------------------------- | +| `make ibmcloud-cli-install` | Install IBM Cloud CLI and required plugins | +| `make ibmcloud-login` | Log in to IBM Cloud (API key or SSO) | +| `make ibmcloud-ce-login` | Select the Code Engine project & region | +| `make ibmcloud-tag` | Tag the local container image | +| `make ibmcloud-push` | Push the image to IBM Container Registry | +| `make ibmcloud-deploy` | **Create or update** the Code Engine application (uses CPU/memory/secret) | +| `make ibmcloud-ce-status` | Show current deployment status | +| `make ibmcloud-ce-logs` | Stream logs from the running app | +| `make ibmcloud-ce-rm` | Delete the Code Engine application | + +--- + +### 📝 Example Workflow + +```bash +make ibmcloud-check-env +make ibmcloud-cli-install +make ibmcloud-login +make ibmcloud-ce-login +make ibmcloud-tag +make ibmcloud-push +make ibmcloud-deploy +make ibmcloud-ce-status +make ibmcloud-ce-logs +``` + +--- + +## API Endpoints + +### Protocol APIs (MCP) + +```bash +# Initialize MCP session +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ + "protocol_version":"2025-03-26", + "capabilities":{}, + "client_info":{"name":"MyClient","version":"1.0.0"} + }' \ + http://localhost:4444/protocol/initialize + +# Ping (JSON-RPC style) +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"ping"}' \ + http://localhost:4444/protocol/ping + +# Completion for prompt/resource arguments +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ + "ref":{"type":"ref/prompt","name":"example_prompt"}, + "argument":{"name":"topic","value":"py"} + }' \ + http://localhost:4444/protocol/completion/complete + +# Sampling (streaming) +curl -N -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ + "messages":[{"role":"user","content":{"type":"text","text":"Hello"}}], + "maxTokens":16 + }' \ + http://localhost:4444/protocol/sampling/createMessage +``` + +--- + +### JSON-RPC Utility Endpoint + +```bash +# Generic JSON-RPC calls (tools, gateways, roots, etc.) +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"list_tools"}' \ + http://localhost:4444/rpc +``` + +Handles any method name: `list_tools`, `list_gateways`, `prompts/get`, or invokes a tool if method matches a registered tool name . + +--- + +### Tool Management (`/tools`) + +```bash +# Register a new tool +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ + "name":"clock_tool", + "url":"http://localhost:9000/rpc", + "description":"Returns current time", + "input_schema":{ + "type":"object", + "properties":{"timezone":{"type":"string"}}, + "required":[] + } + }' \ + http://localhost:4444/tools + +# List tools +curl -u admin:changeme http://localhost:4444/tools + +# Get tool by ID +curl -u admin:changeme http://localhost:4444/tools/1 + +# Update tool +curl -X PUT -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ "description":"Updated desc" }' \ + http://localhost:4444/tools/1 + +# Toggle active status +curl -X POST -u admin:changeme \ + http://localhost:4444/tools/1/toggle?activate=false + +# Delete tool +curl -X DELETE -u admin:changeme http://localhost:4444/tools/1 +``` + +--- + +### Gateway Management (`/gateways`) + +```bash +# Register a peer gateway +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"name":"peer_gateway","url":"http://peer:4444"}' \ + http://localhost:4444/gateways + +# List gateways +curl -u admin:changeme http://localhost:4444/gateways + +# Get gateway by ID +curl -u admin:changeme http://localhost:4444/gateways/1 + +# Update gateway +curl -X PUT -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"description":"New description"}' \ + http://localhost:4444/gateways/1 + +# Toggle active status +curl -X POST -u admin:changeme \ + http://localhost:4444/gateways/1/toggle?activate=false + +# Delete gateway +curl -X DELETE -u admin:changeme http://localhost:4444/gateways/1 +``` + +--- + +### Resource Management (`/resources`) + +```bash +# Register resource +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ + "uri":"config://app/settings", + "name":"App Settings", + "content":"key=value" + }' \ + http://localhost:4444/resources + +# List resources +curl -u admin:changeme http://localhost:4444/resources + +# Read a resource +curl -u admin:changeme http://localhost:4444/resources/config://app/settings + +# Update resource +curl -X PUT -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"content":"new=value"}' \ + http://localhost:4444/resources/config://app/settings + +# Delete resource +curl -X DELETE -u admin:changeme http://localhost:4444/resources/config://app/settings + +# Subscribe to updates (SSE) +curl -N -u admin:changeme http://localhost:4444/resources/subscribe/config://app/settings +``` + +--- + +### Prompt Management (`/prompts`) + +```bash +# Create prompt template +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{ + "name":"greet", + "template":"Hello, {{ user }}!", + "argument_schema":{ + "type":"object", + "properties":{"user":{"type":"string"}}, + "required":["user"] + } + }' \ + http://localhost:4444/prompts + +# List prompts +curl -u admin:changeme http://localhost:4444/prompts + +# Get prompt (with args) +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"user":"Alice"}' \ + http://localhost:4444/prompts/greet + +# Get prompt (no args) +curl -u admin:changeme http://localhost:4444/prompts/greet + +# Update prompt +curl -X PUT -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"template":"Hi, {{ user }}!"}' \ + http://localhost:4444/prompts/greet + +# Toggle active +curl -X POST -u admin:changeme \ + http://localhost:4444/prompts/5/toggle?activate=false + +# Delete prompt +curl -X DELETE -u admin:changeme http://localhost:4444/prompts/greet +``` + +--- + +### Root Management (`/roots`) + +```bash +# List roots +curl -u admin:changeme http://localhost:4444/roots + +# Add root +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"uri":"/data","name":"Data Root"}' \ + http://localhost:4444/roots + +# Remove root +curl -X DELETE -u admin:changeme http://localhost:4444/roots/%2Fdata + +# Subscribe to root changes (SSE) +curl -N -u admin:changeme http://localhost:4444/roots/changes +``` + +--- + +### Server Management (`/servers`) + +```bash +# List servers +curl -u admin:changeme http://localhost:4444/servers + +# Get server +curl -u admin:changeme http://localhost:4444/servers/1 + +# Create server +curl -X POST -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"name":"db","description":"Database"}' \ + http://localhost:4444/servers + +# Update server +curl -X PUT -u admin:changeme \ + -H "Content-Type: application/json" \ + -d '{"description":"Updated"}' \ + http://localhost:4444/servers/1 + +# Toggle active +curl -X POST -u admin:changeme \ + http://localhost:4444/servers/1/toggle?activate=false +``` + +--- + +### Metrics (`/metrics`) + +```bash +# Get aggregated metrics +curl -u admin:changeme http://localhost:4444/metrics + +# Reset metrics (all or per-entity) +curl -X POST -u admin:changeme http://localhost:4444/metrics/reset +curl -X POST -u admin:changeme http://localhost:4444/metrics/reset?entity=tool&id=1 +``` + +--- + +### Event Streaming & Health + +```bash +# SSE: all events +curl -N -u admin:changeme http://localhost:4444/events + +# WebSocket +wscat -c ws://localhost:4444/ws \ + -H "Authorization: Basic $(echo -n admin:changeme|base64)" + +# Health check +curl http://localhost:4444/health +``` + +Full Swagger UI at `/docs`. + +--- + +## Sample Tool + +```bash +uvicorn sample_tool.clock_tool:app --host 0.0.0.0 --port 9000 +``` + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"get_time","params":{"timezone":"UTC"}}' \ + http://localhost:9000/rpc +``` + +--- + +## Testing + +```bash +make tests # Run unit tests +make lint # Run lint tools +``` + +--- + +## Project Structure + +```bash +. +├── Containerfile # OCI image build for Docker and Podman +├── DEVELOPING.md # Contributor guidelines, workflows, and style guide +├── dictionary.dic # Custom dictionary for spell-checker and linters +├── gunicorn.config.py # Production Gunicorn settings (workers, logging, timeouts) +├── LICENSE # Apache License 2.0 +├── Makefile # Development & deployment targets: venv, install, tests, serve, podman, etc. +├── mcp.db # Default SQLite database file (auto-created on first run) +├── mcpgateway # ← main application package +│ ├── __init__.py # Package metadata and version constant +│ ├── admin.py # FastAPI routers and controllers for the Admin UI +│ ├── cache +│ │ ├── __init__.py # Cache package initializer +│ │ └── resource_cache.py # In-memory LRU+TTL cache implementation for resources +│ ├── config.py # Pydantic Settings loader (env, .env parsing) +│ ├── db.py # SQLAlchemy ORM models, engine setup, and migrations +│ ├── federation +│ │ ├── __init__.py # Federation package initializer +│ │ ├── discovery.py # Peer gateway discovery (mDNS, explicit lists) +│ │ ├── forward.py # RPC forwarding logic to peer gateways +│ │ └── manager.py # Federation orchestration, health checks, capability aggregation +│ ├── handlers +│ │ ├── __init__.py # Handlers package initializer +│ │ └── sampling.py # MCP streaming sampling request handler +│ ├── main.py # FastAPI app factory, startup/shutdown events +│ ├── schemas.py # Shared Pydantic DTOs for requests and responses +│ ├── services +│ │ ├── __init__.py # Services package initializer +│ │ ├── completion_service.py # Prompt and resource argument completion logic +│ │ ├── gateway_service.py # Peer gateway registration and management +│ │ ├── logging_service.py # Central logging helpers and middleware +│ │ ├── prompt_service.py # Prompt template CRUD, validation, rendering +│ │ ├── resource_service.py # Resource registration, retrieval, templates, subscriptions +│ │ ├── root_service.py # File-system root registry and subscriptions +│ │ ├── server_service.py # Server registry, health monitoring +│ │ └── tool_service.py # Tool registration, invocation, metrics +│ ├── static # Tailwind CSS, JavaScript and other static assets for Admin UI +│ ├── templates +│ │ └── admin.html # HTMX/Alpine.js Admin UI HTML template +│ ├── static +│ │ ├── admin.js # JS functions for Admin UI +│ │ ├── admin.css # Styles for Admin UI +│ ├── transports +│ │ ├── __init__.py # Transports package initializer +│ │ ├── base.py # Abstract Transport interface (connect, send, receive) +│ │ ├── sse_transport.py # Server-Sent Events transport implementation +│ │ ├── stdio_transport.py # stdio transport for subprocess embedding +│ │ └── websocket_transport.py # WebSocket transport with ping/pong support +│ ├── types.py # Core enums, type aliases, shared data classes +│ ├── utils +│ │ ├── create_jwt_token.py # CLI & library for JWT generation and inspection +│ │ └── verify_credentials.py # FastAPI deps for Basic and JWT authentication +│ └── validation +│ ├── __init__.py # Validation package initializer +│ └── jsonrpc.py # JSON-RPC 2.0 request and response validation +├── os_deps.sh # Script to install system-level dependencies +├── pdm.lock # PDM dependency lockfile +├── pyproject.toml # Poetry/PDM configuration, dependencies, lint rules +├── run-gunicorn.sh # Opinionated Gunicorn startup script +├── run.sh # Uvicorn shortcut script with argument parsing +├── TESTING.md # Testing strategy, fixtures and guidelines +├── test_readme.py # CI check to ensure README stays in sync +└── tests + ├── conftest.py # Shared fixtures and pytest setup + ├── e2e # End-to-end test scenarios + ├── integration # API-level integration tests + └── unit # Pure unit tests for business logic +``` + +--- + +## API Documentation + +* **Swagger UI** → [http://localhost:4444/docs](http://localhost:4444/docs) +* **ReDoc**    → [http://localhost:4444/redoc](http://localhost:4444/redoc) +* **Admin Panel** → [http://localhost:4444/admin](http://localhost:4444/admin) + +--- + +## Makefile targets + +This project offer the following Makefile targets. Type `make` in the project root to show all targets: + +```bash +🐍 MCP CONTEXT FORGE (An enterprise-ready Model Context Protocol Gateway) +🔧 SYSTEM-LEVEL DEPENDENCIES (DEV BUILD ONLY) +os-deps - Install Graphviz, Pandoc, Trivy, SCC used for dev docs generation and security scan +🌱 VIRTUAL ENVIRONMENT & INSTALLATION +venv - Create a fresh virtual environment with uv & friends +activate - Activate the virtual environment in the current shell +install - Install project into the venv +install-dev - Install project (incl. dev deps) into the venv +update - Update all installed deps inside the venv +check-env - Verify all required env vars in .env are present +▶️ SERVE & TESTING +serve - Run production Gunicorn server on :4444 +certs - Generate self-signed TLS cert & key in ./certs (won't overwrite) +serve-ssl - Run Gunicorn behind HTTPS on :4444 (uses ./certs) +dev - Run fast-reload dev server (uvicorn) +run - Execute helper script ./run.sh +test - Run unit tests with pytest +test-curl - Smoke-test API endpoints with curl script +pytest-examples - Run README / examples through pytest-examples +clean - Remove caches, build artefacts, virtualenv, docs, certs, coverage, SBOM, etc. +📊 COVERAGE & METRICS +coverage - Run tests with coverage, emit md/HTML/XML + badge +pip-licenses - Produce dependency license inventory (markdown) +scc - Quick LoC/complexity snapshot with scc +scc-report - Generate HTML LoC & per-file metrics with scc +📚 DOCUMENTATION & SBOM +docs - Build docs (graphviz + handsdown + images + SBOM) +images - Generate architecture & dependency diagrams +🔍 LINTING & STATIC ANALYSIS +lint - Run the full linting suite (see targets below) +black - Reformat code with black +autoflake - Remove unused imports / variables with autoflake +isort - Organise & sort imports with isort +flake8 - PEP-8 style & logical errors +pylint - Pylint static analysis +mypy - Static type-checking with mypy +bandit - Security scan with bandit +pydocstyle - Docstring style checker +pycodestyle - Simple PEP-8 checker +pre-commit - Run all configured pre-commit hooks +ruff - Ruff linter + formatter +ty - Ty type checker from astral +pyright - Static type-checking with Pyright +radon - Code complexity & maintainability metrics +pyroma - Validate packaging metadata +importchecker - Detect orphaned imports +spellcheck - Spell-check the codebase +fawltydeps - Detect undeclared / unused deps +wily - Maintainability report +pyre - Static analysis with Facebook Pyre +depend - List dependencies in ≈requirements format +snakeviz - Profile & visualise with snakeviz +pstats - Generate PNG call-graph from cProfile stats +spellcheck-sort - Sort local spellcheck dictionary +tox - Run tox across multi-Python versions +sbom - Produce a CycloneDX SBOM and vulnerability scan +pytype - Flow-sensitive type checker +check-manifest - Verify sdist/wheel completeness +🕸️ WEBPAGE LINTERS & STATIC ANALYSIS (HTML/CSS/JS lint + security scans + formatting) +install-web-linters - Install HTMLHint, Stylelint, ESLint, Retire.js & Prettier via npm +lint-web - Run HTMLHint, Stylelint, ESLint, Retire.js and npm audit +format-web - Format HTML, CSS & JS files with Prettier +📡 SONARQUBE ANALYSIS +sonar-deps-podman - Install podman-compose + supporting tools +sonar-deps-docker - Install docker-compose + supporting tools +sonar-up-podman - Launch SonarQube with podman-compose +sonar-up-docker - Launch SonarQube with docker-compose +sonar-submit-docker - Run containerised Sonar Scanner CLI with Docker +sonar-submit-podman - Run containerised Sonar Scanner CLI with Podman +pysonar-scanner - Run scan with Python wrapper (pysonar-scanner) +sonar-info - How to create a token & which env vars to export +🛡️ SECURITY & PACKAGE SCANNING +trivy - Scan container image for CVEs (HIGH/CRIT). Needs podman socket enabled +dockle - Lint the built container image via tarball (no daemon/socket needed) +hadolint - Lint Containerfile/Dockerfile(s) with hadolint +pip-audit - Audit Python dependencies for published CVEs +📦 DEPENDENCY MANAGEMENT +deps-update - Run update-deps.py to update all dependencies in pyproject.toml and docs/requirements.txt +containerfile-update - Update base image in Containerfile to latest tag +🦭 PODMAN CONTAINER BUILD & RUN +podman-dev - Build development container image +podman - Build production container image +podman-run - Run the container on HTTP (port 4444) +podman-run-shell - Run the container on HTTP (port 4444) and start a shell +podman-run-ssl - Run the container on HTTPS (port 4444, self-signed) +podman-stop - Stop & remove the container +podman-test - Quick curl smoke-test against the container +podman-logs - Follow container logs (⌃C to quit) +podman-stats - Show container resource stats (if supported) +podman-top - Show live top-level process info in container +podman-shell - Open an interactive shell inside the Podman container +🐋 DOCKER BUILD & RUN +docker-dev - Build development Docker image +docker - Build production Docker image +docker-run - Run the container on HTTP (port 4444) +docker-run-ssl - Run the container on HTTPS (port 4444, self-signed) +docker-stop - Stop & remove the container +docker-test - Quick curl smoke-test against the container +docker-logs - Follow container logs (⌃C to quit) +docker-stats - Show container resource usage stats (non-streaming) +docker-top - Show top-level process info in Docker container +docker-shell - Open an interactive shell inside the Docker container +☁️ IBM CLOUD CODE ENGINE +ibmcloud-check-env - Verify all required IBM Cloud env vars are set +ibmcloud-cli-install - Auto-install IBM Cloud CLI + required plugins (OS auto-detected) +ibmcloud-login - Login to IBM Cloud CLI using IBMCLOUD_API_KEY (--sso) +ibmcloud-ce-login - Set Code Engine target project and region +ibmcloud-list-containers - List deployed Code Engine apps +ibmcloud-tag - Tag container image for IBM Container Registry +ibmcloud-push - Push image to IBM Container Registry +ibmcloud-deploy - Deploy (or update) container image in Code Engine +ibmcloud-ce-logs - Stream logs for the deployed application +ibmcloud-ce-status - Get deployment status +ibmcloud-ce-rm - Delete the Code Engine application +``` + +## Contributing + +1. Fork the repo, create a feature branch. +2. Run `make lint` and fix any issues. +3. Keep `make test` green and 100% coverage. +4. Open a PR – describe your changes clearly. + +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details. +--- + +## License + +Licensed under the **Apache License 2.0** – see `LICENSE`. + + +## Authors + +- Mihai Criveti - Distinguished Engineer, Agentic AI diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000000..38ece3450c --- /dev/null +++ b/TESTING.md @@ -0,0 +1,103 @@ +# Testing Guide for MCP Context Forge + +This guide explains how to set up and run tests for the MCP Context Forge project. + +## Prerequisites + +- Python 3.10 or higher +- virtualenv or venv (for virtual environment management) +- Make (for running Makefile commands) + +## Setting Up the Test Environment + +First, create a virtual environment and install the project's development dependencies: + +```bash +make venv # Create a virtual environment +make install # Install the project with development dependencies +``` + +## Running Tests + +### Running All Tests + +To run all tests, simply use: + +```bash +make test +``` + +This will: +1. Create a virtual environment if it doesn't exist +2. Install required testing dependencies (pytest, pytest-asyncio, pytest-cov) +3. Run the pytest suite with verbose output + +### Running Specific Tests + +You can run specific tests by specifying the file or directory: + +```bash +# Activate the virtual environment +source ~/.venv/mcpgateway/bin/activate + +# Run a specific test file +python -m pytest tests/unit/mcpgateway/test_config.py -v + +# Run a specific test class +python -m pytest tests/unit/mcpgateway/validation/test_jsonrpc.py::TestJSONRPCValidation -v + +# Run a specific test method +python -m pytest tests/unit/mcpgateway/validation/test_jsonrpc.py::TestJSONRPCValidation::test_validate_valid_request -v +``` + +### Testing README Examples + +To test code examples from the README: + +```bash +make pytest-examples +``` + +## Test Coverage + +To run tests with coverage reporting: + +```bash +# Activate the virtual environment +source ~/.venv/mcpgateway/bin/activate + +# Run tests with coverage +python -m pytest --cov=mcpgateway tests/ + +# Generate a coverage report +python -m pytest --cov=mcpgateway --cov-report=html tests/ +``` + +The HTML coverage report will be available in the `htmlcov` directory. + +## Creating New Tests + +When creating new tests, follow these guidelines: + +1. Place test files in the appropriate directory under `tests/unit/` +2. Use the naming convention `test_*.py` for test files +3. Use pytest fixtures from `conftest.py` where applicable +4. Use `@pytest.mark.asyncio` decorator for asynchronous tests + +## Continuous Integration + +The project is configured to run tests automatically in CI/CD pipelines. +When committing changes, ensure all tests pass locally first: + +```bash +make test +``` + +## Troubleshooting + +If you encounter issues with running tests: + +1. Check that you're using the virtual environment with the correct dependencies +2. Verify that your Python version is compatible (Python 3.10+) +3. Try recreating the virtual environment: `make clean && make venv && make install` +4. Check for any error messages during dependency installation diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..65db955736 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +env +site/ +.idea/ +*.swp +build.log +.cache diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000..4612b4194d --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,94 @@ +# Author: Mihai Criveti + +# Configuration section +HANDBOOK_NAME := mcpgateway-docs + +# Do not change anything below this line +VENVS_DIR := $(HOME)/.venv +VENV_DIR := $(VENVS_DIR)/$(HANDBOOK_NAME) + +# Setup to support M1 Macs - Linux will be empty; x86 Macs essentially NO-OP +BREW_SHELLENV := $(shell brew shellenv 2>/dev/null) + +.PHONY: help +help: + @grep "^# help\:" Makefile | grep -v grep | sed 's/\# help\: //' | sed 's/\# help\://' + +# help: MAINTAIN +# help: venv - create a clean virtual environment for development +.PHONY: venv +venv: + @test -d "$(VENVS_DIR)" || mkdir -p "$(VENVS_DIR)" + @rm -Rf "$(VENV_DIR)" + @eval '$(BREW_SHELLENV)' && python3 -m venv "$(VENV_DIR)" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && pip install --upgrade pip setuptools wheel && pip install markdown-blockdiag && pip install -r requirements.txt" + @echo -e "Enter virtual environment using:\n. $(VENV_DIR)/bin/activate\n" + + +# help: venv-update - update a virtual environment for development +.PHONY: venv-update +venv-update: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && pip install --upgrade pip setuptools wheel && pip install --upgrade markdown-blockdiag && pip install --upgrade -r requirements.txt" + @echo -e "Enter virtual environment using:\n. $(VENV_DIR)/bin/activate\n" + + +# help: activate - enter existing venv +.PHONY: activate +activate: + @echo -e "Enter virtual environment using:\n. $(VENV_DIR)/bin/activate\n" + @. $(VENV_DIR)/bin/activate + + +# help: clean - clean all files +.PHONY: clean +clean: + @rm -rf ./site + + +# help: git-clean - clean all files using .gitignore rules +.PHONY: git-clean +git-clean: + @git clean -X -f -d + + +# help: git-scrub - clean all files, even untracked files +.PHONY: git-scrub +git-scrub: + @git clean -x -f -d + +# help: +# help: MKDOCS +# help: serve - serve project html documentation +.PHONY: serve +serve: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && mkdocs serve" + +# help: build - serve project html documentation +.PHONY: build +build: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && mkdocs build > $(HANDBOOK_NAME).html" + @/bin/bash -c "sed -i'' -e '1{/^Converting/d;}' $(HANDBOOK_NAME).html" + @/bin/bash -c "sed -i'' -e 's#file://$(shell git rev-parse --show-toplevel)/##g' $(HANDBOOK_NAME).html" + @/bin/bash -c "mkdir -p site/out" + @/bin/bash -c "pandoc $(HANDBOOK_NAME).html -o $(HANDBOOK_NAME).docx" + @/bin/bash -c "mv $(HANDBOOK_NAME).html site/out/" + @/bin/bash -c "mv $(HANDBOOK_NAME).docx site/out/" + +# help: package - package the sources for box deployment +.PHONY: package +package: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && mkdocs build && mkdir -p release && tar cvvf release/$(HANDBOOK_NAME)-Package-$$(date --iso).tar.gz --exclude='release' . " + + +# help: deploy - deploy project to github pages +.PHONY: deploy +deploy: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && mkdocs gh-deploy" + +# help: combine - combine +.PHONY: combine +combine: + @/bin/bash -c "source $(VENV_DIR)/bin/activate && mkdocscombine -o out/$(HANDBOOK_NAME)-combined.md" + +# Keep these lines at the end of the file to retain output formatting. +# help: diff --git a/docs/base.yml b/docs/base.yml new file mode 100644 index 0000000000..81e03397c8 --- /dev/null +++ b/docs/base.yml @@ -0,0 +1,163 @@ +# DO NOT EDIT, see mkdocs.yml + +# Repository Information +edit_uri: 'edit/main/docs' +docs_dir: 'docs' +site_dir: 'site' +remote_branch: 'gh-pages' +remote_name: 'origin' + +# Server info +dev_addr: '127.0.0.1:8003' + +# Theme +theme: + name: material + custom_dir: theme + + # Static files + static_templates: + - 404.html + + # Don't include MkDocs' JavaScript + include_search_page: false + search_index_only: true + + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: false + favicon: 'favicon.ico' + logo: 'logo.png' + + +# hide: +# - navigation # Hide left navigation +# - toc # Hide right navigation + +# extra: +# generator: false # This is an insiders feature + +# Extra stylesheet +# extra_css: +# - 'theme.css' +# - 'pdf.css' + +# Extra links as icons at the bottom of the page +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/IBM/mcp-context-forge + +# Configure the Markdown extensions for magic links +markdown_extensions: + admonition: {} + attr_list: {} + def_list: {} + tables: {} + abbr: {} + footnotes: {} + md_in_html: {} + meta: {} + smarty: + smart_quotes: false + pymdownx.betterem: {} + pymdownx.critic: {} + pymdownx.superfences: + preserve_tabs: true + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - name: flow + class: uml-flowchart + format: !!python/name:pymdownx.superfences.fence_code_format + - name: sequence + class: uml-sequence-diagram + format: !!python/name:pymdownx.superfences.fence_code_format + - name: math + class: arithmatex + format: !!python/name:pymdownx.arithmatex.fence_mathjax_format + pymdownx.highlight: + anchor_linenums: true + extend_pygments_lang: + - name: php-inline + lang: php + options: + startinline: true + - name: pycon3 + lang: pycon + options: + python3: true + linenums_style: pymdownx-inline + line_spans: __span + pygments_lang_class: true + pymdownx.inlinehilite: + custom_inline: + - name: math + class: arithmatex + format: !!python/name:pymdownx.arithmatex.inline_mathjax_format + pymdownx.tilde: {} + pymdownx.caret: {} + pymdownx.smartsymbols: {} + pymdownx.emoji: + emoji_generator: !!python/name:pymdownx.emoji.to_png + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + options: + custom_icons: + - docs/_icons + pymdownx.escapeall: + hardbreak: true + nbsp: true + pymdownx.tasklist: + custom_checkbox: true + pymdownx.progressbar: {} + pymdownx.arithmatex: {} + pymdownx.mark: {} + pymdownx.striphtml: {} + # pymdownx.snippets: + # auto_append: + # - includes/abbreviations.md + pymdownx.keys: + separator: "\uff0b" + pymdownx.details: {} + pymdownx.tabbed: + alternate_style: true + +plugins: + search: {} + inline-svg: {} + table-reader: {} + git-revision-date-localized: + fallback_to_build_date: true + git-authors: {} + glightbox: {} + awesome-pages: + filename: .pages + collapse_single_pages: false + strict: false + minify: + minify_html: true + pdf-export: + verbose: false + media_type: print + enabled_if_env: ENABLE_PDF_EXPORT + combined: false + rss: + match_path: blog/posts/.* + date_from_meta: + as_creation: date + categories: + - categories + - tags diff --git a/docs/create.sh b/docs/create.sh new file mode 100755 index 0000000000..247a15ee59 --- /dev/null +++ b/docs/create.sh @@ -0,0 +1,89 @@ +#!/bin/bash +set -e + +# Base structure +mkdir -p docs/{overview,development,deployment,manage,using/clients,using/agents} + +# Root index +touch docs/index.md + +# --- .pages files and index.md for each top-level section --- + +# Overview +cat > docs/overview/.pages < docs/development/.pages < docs/deployment/.pages < docs/manage/.pages < docs/using/.pages < docs/using/clients/.pages < docs/using/agents/.pages < my-elb-1234.us-west-2.elb.amazonaws.com +``` + +--- diff --git a/docs/docs/deployment/azure.md b/docs/docs/deployment/azure.md new file mode 100644 index 0000000000..d4a0a3dceb --- /dev/null +++ b/docs/docs/deployment/azure.md @@ -0,0 +1,84 @@ +# Azure Deployment + +MCP Gateway can be deployed on Azure in multiple ways: + +- **Azure Container Apps** (serverless) +- **Azure App Service** (PaaS for containers) +- **Azure Kubernetes Service (AKS)** (fully managed K8s) + +--- + +## 🚀 Option 1: Azure Container Apps (Recommended) + +Azure Container Apps is ideal for lightweight container-based workloads. + +### Steps + +1. **Build and push your image to Azure Container Registry (ACR):** + +```bash +az acr login --name yourregistry +docker tag mcpgateway yourregistry.azurecr.io/mcpgateway +docker push yourregistry.azurecr.io/mcpgateway +``` + +2. **Create the container app:** + +```bash +az containerapp create \ + --name mcpgateway \ + --resource-group my-rg \ + --image yourregistry.azurecr.io/mcpgateway \ + --target-port 4444 \ + --environment my-container-env \ + --registry-server yourregistry.azurecr.io \ + --env-vars-from-secrets .env +``` + +> You can mount `.env` via Key Vault or inject environment variables directly. + +--- + +## 🚀 Option 2: Azure App Service + +1. Push your image to ACR +2. Create an App Service plan and container-based Web App +3. Set `PORT=4444` and other env vars in Configuration → Application settings +4. Map your custom domain (optional) + +--- + +## 🚀 Option 3: Azure Kubernetes Service (AKS) + +Use your existing [Kubernetes deployment](kubernetes.md) instructions, but deploy to AKS. + +* Deploy with Helm or `kubectl` +* Use Azure Load Balancer or Application Gateway +* Store secrets in Azure Key Vault (optional) + +--- + +## 🔐 Secrets & Config + +Use Azure CLI to upload your `.env` values to App Config or Key Vault: + +```bash +az keyvault secret set --vault-name my-kv --name JWT-SECRET --value "super-secret" +``` + +Then reference in App Service / Container App using environment variables. + +--- + +## 📡 DNS & TLS + +* Use Azure Front Door or Application Gateway to handle TLS +* Point your domain to the public IP or hostname of the service + +Example: + +```bash +gateway.example.com → mygateway.eastus.azurecontainerapps.io +``` + +--- diff --git a/docs/docs/deployment/container.md b/docs/docs/deployment/container.md new file mode 100644 index 0000000000..d052fe6adc --- /dev/null +++ b/docs/docs/deployment/container.md @@ -0,0 +1,83 @@ +# Container Deployment + +You can run MCP Gateway as a fully self-contained container. This is the recommended method for production or platform-agnostic deployments. + +--- + +## 🐳 Build the Container + +### Using Podman (recommended) + +```bash +make podman +``` + +### Using Docker (manual alternative) + +```bash +docker build -t mcpgateway:latest -f Containerfile . +``` + +> The base image uses `python:3.11-slim` with Gunicorn and Uvicorn workers. + +--- + +## 🏃 Run the Container + +### With HTTP (no TLS) + +```bash +make podman-run +``` + +This starts the app at `http://localhost:4444`. + +--- + +### With Self-Signed TLS (HTTPS) + +```bash +make podman-run-ssl +``` + +Runs the gateway using certs from `./certs/`, available at: + +``` +https://localhost:4444 +``` + +--- + +## ⚙ Runtime Configuration + +All environment variables can be passed via: + +* `docker run -e KEY=value` +* A mounted `.env` file (`--env-file .env`) + +--- + +## 🧪 Test the Running Container + +```bash +curl http://localhost:4444/health +curl http://localhost:4444/tools +``` + +> Use `curl -k` if running with self-signed TLS + +--- + +## 🧼 Stop & Clean Up + +```bash +podman stop mcpgateway +podman rm mcpgateway +``` + +Or with Docker: + +```bash +docker stop mcpgateway +docker rm mcpgateway +``` diff --git a/docs/docs/deployment/ibm-code-engine.md b/docs/docs/deployment/ibm-code-engine.md new file mode 100644 index 0000000000..af54fb1bb4 --- /dev/null +++ b/docs/docs/deployment/ibm-code-engine.md @@ -0,0 +1,453 @@ +# Deploying MCP Gateway to IBM Code Engine + +This guide covers two supported deployment paths for the **MCP Gateway**: + +1. **Makefile automation** – a single-command workflow that wraps `ibmcloud` CLI. +2. **Manual IBM Cloud CLI** – the raw commands the Makefile executes, for fine-grained control. + +--- + +## 1 · Prerequisites + +| Requirement | Details | +| -------------------- | ------------------------------------------------------------------ | +| IBM Cloud account | [Create one](https://cloud.ibm.com/registration) if needed | +| Docker **or** Podman | Builds the production container image locally | +| IBM Cloud CLI ≥ 2.16 | Installed automatically with `make ibmcloud-cli-install` | +| Code Engine project | Create or select one in the IBM Cloud console | +| `.env` file | Runtime secrets & config for the gateway | +| `.env.ce` file | Deployment credentials & metadata for Code Engine / Container Reg. | + +--- + +## 2 · Environment files + +Both files are already in **`.gitignore`**. +Template named **`.env.example`** **`.env.ce.example`** and are included; copy them: + +```bash +cp .env.example .env # runtime settings (inside the container) +cp .env.ce.example .env.ce # deployment credentials (CLI only) +``` + +### `.env` – runtime settings + +This file is **mounted into the container** (via `--env-file=.env`), so its keys live inside Code Engine at runtime. Treat it as an application secret store. + +```bash +# ───────────────────────────────────────────────────────────────────────────── +# Core gateway settings +# ───────────────────────────────────────────────────────────────────────────── +AUTH_REQUIRED=true +# Generate once: openssl rand -hex 32 +JWT_SECRET_KEY=eef5e9f70ca7fe6f9677ad2acaf4d32c55e9d98e9cb74299b33f5c5d1a3c8ef + +HOST=0.0.0.0 +PORT=4444 + + +# ───────────────────────────────────────────────────────────────────────────── +# Database configuration – choose ONE block +# ───────────────────────────────────────────────────────────────────────────── + +## (A) Local SQLite (good for smoke-tests / CI only) +## -------------------------------------------------- +## • SQLite lives on the container’s ephemeral file system. +## • On Code Engine every new instance starts fresh; scale-out, restarts or +## deploys will wipe data. **Not suitable for production.** +## • If you still need file persistence, attach Code Engine’s file-system +## mount or an external filesystem / COS bucket. +#CACHE_TYPE=database +#DATABASE_URL=sqlite:////tmp/mcp.db + + +## (B) Managed PostgreSQL on IBM Cloud (recommended for staging/production) +## -------------------------------------------------------------------------- +## • Provision an IBM Cloud Databases for PostgreSQL instance (see below). +## • Use the service credentials to build the URL. +## • sslmode=require is mandatory for IBM Cloud databases. +CACHE_TYPE=database +DATABASE_URL=postgresql://pguser:pgpass@my-pg-host.databases.appdomain.cloud:32727/mcpgwdb?sslmode=require +# │ │ │ │ │ +# │ │ │ │ └─ database name +# │ │ │ └─ hostname:port +# │ │ └─ password +# │ └─ username +# └─ scheme +``` + +The `JWT_SECRET_KEY` variable is used to generate a Bearer token used to access the APIs. +To access the APIs you need to generate your JWT token using the same `JWT_SECRET_KEY`, for example: + +```bash +# Generate a one-off token for the default admin user +export MCPGATEWAY_BEARER_TOKEN=$(python -m mcpgateway.utils.create_jwt_token -u admin) +echo ${MCPGATEWAY_BEARER_TOKEN} # Check that the key was generated +``` + +### `.env.ce` – Code Engine deployment settings + +These keys are **only** consumed by Makefile / CLI. They never reach the running container. + +```bash +# ───────────────────────────────────────────────────────────────────────────── +# IBM Cloud / Code Engine deployment variables +# ───────────────────────────────────────────────────────────────────────────── +IBMCLOUD_REGION=us-south +IBMCLOUD_RESOURCE_GROUP=default +IBMCLOUD_PROJECT=my-codeengine-project +IBMCLOUD_CODE_ENGINE_APP=mcpgateway + +# Image details +IBMCLOUD_IMAGE_NAME=us.icr.io/myspace/mcpgateway:latest # target in IBM Container Registry +IBMCLOUD_IMG_PROD=mcpgateway/mcpgateway # local tag produced by Make + +# Authentication +IBMCLOUD_API_KEY=***your-api-key*** # leave blank to use SSO flow at login + +# Resource combo – see https://cloud.ibm.com/docs/codeengine?topic=codeengine-mem-cpu-combo +IBMCLOUD_CPU=1 # vCPU for the container +IBMCLOUD_MEMORY=4G # Memory (must match a valid CPU/MEM pair) + +# Registry secret in Code Engine (first-time creation is automated) +IBMCLOUD_REGISTRY_SECRET=my-regcred +``` + +> **Tip:** run `make ibmcloud-check-env` to verify every required `IBMCLOUD_*` key is present in `.env.ce`. + +--- + +## 3 · Workflow A – Makefile targets + +| Target | Action it performs | +| --------------------------- | ------------------------------------------------------------------------------------ | +| **`podman`** / **`docker`** | Build the production image (`$IBMCLOUD_IMG_PROD`). | +| `ibmcloud-cli-install` | Install IBM Cloud CLI + **container-registry** and **code-engine** plugins. | +| `ibmcloud-check-env` | Ensure all `IBMCLOUD_*` vars exist in `.env.ce`; abort if any are missing. | +| `ibmcloud-login` | `ibmcloud login` – uses API key or interactive SSO. | +| `ibmcloud-ce-login` | `ibmcloud ce project select --name $IBMCLOUD_PROJECT`. | +| `ibmcloud-list-containers` | Show ICR images and existing Code Engine apps. | +| `ibmcloud-tag` | `podman tag $IBMCLOUD_IMG_PROD $IBMCLOUD_IMAGE_NAME`. | +| `ibmcloud-push` | `ibmcloud cr login` + `podman push` to ICR. | +| `ibmcloud-deploy` | Create **or** update the app, set CPU/MEM, attach registry secret, expose port 4444. | +| `ibmcloud-ce-status` | `ibmcloud ce application get` – see route URL, revisions, health. | +| `ibmcloud-ce-logs` | `ibmcloud ce application logs --follow` – live log stream. | +| `ibmcloud-ce-rm` | Delete the application entirely. | + +**Typical first deploy** + +```bash +make ibmcloud-check-env +make ibmcloud-cli-install +make ibmcloud-login +make ibmcloud-ce-login +make podman # or: make docker +make ibmcloud-tag +make ibmcloud-push +make ibmcloud-deploy +``` + +**Redeploy after code changes** + +```bash +make podman ibmcloud-tag ibmcloud-push ibmcloud-deploy +``` + +--- + +## 4 · Workflow B – Manual IBM Cloud CLI + +```bash +# 1 · Install CLI + plugins +curl -fsSL https://clis.cloud.ibm.com/install/linux | sh +ibmcloud plugin install container-registry -f +ibmcloud plugin install code-engine -f + +# 2 · Login +ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$IBMCLOUD_REGION" -g "$IBMCLOUD_RESOURCE_GROUP" +ibmcloud resource groups # list resource groups + +# 3 · Target Code Engine project +ibmcloud ce project list # list current projects +ibmcloud ce project select --name "$IBMCLOUD_PROJECT" + +# 4 · Build + tag image +podman build -t "$IBMCLOUD_IMG_PROD" . +podman tag "$IBMCLOUD_IMG_PROD" "$IBMCLOUD_IMAGE_NAME" + +# 5 · Push image to ICR +ibmcloud cr login +ibmcloud cr namespaces # Ensure your namespace exists +podman push "$IBMCLOUD_IMAGE_NAME" +ibmcloud cr images # list images + +# 6 · Create registry secret (first time) +ibmcloud ce registry create-secret --name "$IBMCLOUD_REGISTRY_SECRET" \ + --server "$(echo "$IBMCLOUD_IMAGE_NAME" | cut -d/ -f1)" \ + --username iamapikey --password "$IBMCLOUD_API_KEY" +ibmcloud ce secret list # list every secret (generic, registry, SSH, TLS, etc.) +ibmcloud ce secret get --name "$IBMCLOUD_REGISTRY_SECRET" # add --decode to see clear-text values + +# 7 · Deploy / update +if ibmcloud ce application get --name "$IBMCLOUD_CODE_ENGINE_APP" >/dev/null 2>&1; then + ibmcloud ce application update --name "$IBMCLOUD_CODE_ENGINE_APP" \ + --image "$IBMCLOUD_IMAGE_NAME" \ + --cpu "$IBMCLOUD_CPU" --memory "$IBMCLOUD_MEMORY" \ + --registry-secret "$IBMCLOUD_REGISTRY_SECRET" +else + ibmcloud ce application create --name "$IBMCLOUD_CODE_ENGINE_APP" \ + --image "$IBMCLOUD_IMAGE_NAME" \ + --cpu "$IBMCLOUD_CPU" --memory "$IBMCLOUD_MEMORY" \ + --port 4444 \ + --registry-secret "$IBMCLOUD_REGISTRY_SECRET" +fi + +# 8 · Status & logs +ibmcloud ce application get --name "$IBMCLOUD_CODE_ENGINE_APP" +ibmcloud ce application events --name "$IBMCLOUD_CODE_ENGINE_APP" +ibmcloud ce application get --name "$IBMCLOUD_CODE_ENGINE_APP" +ibmcloud ce application logs --name "$IBMCLOUD_CODE_ENGINE_APP" --follow +``` + +--- + +## 5 · Accessing the gateway + +```bash +ibmcloud ce application get --name "$IBMCLOUD_CODE_ENGINE_APP" --output url +``` + +Open the returned URL (e.g. +`https://mcpgateway.us-south.codeengine.appdomain.cloud/admin`) and log in with the basic-auth credentials from `.env`. + +Test the API endpoints with the generated `MCPGATEWAY_BEARER_TOKEN`: + +```bash +# Generate a one-off token for the default admin user +export MCPGATEWAY_BEARER_TOKEN=$(python -m mcpgateway.utils.create_jwt_token -u admin) + +# Call a protected endpoint. Since there are not tools, initially this just returns `[]` +curl -H "Authorization: Bearer ${MCPGATEWAY_BEARER_TOKEN}" \ + https://mcpgateway.us-south.codeengine.appdomain.cloud/tools + +# Check the logs +make ibmcloud-ce-logs +``` + +--- + +## 6 · Cleanup + +```bash +# via Makefile +make ibmcloud-ce-rm + +# or directly +ibmcloud ce application delete --name "$IBMCLOUD_CODE_ENGINE_APP" -f +``` + +--- + +## 7 · Using IBM Cloud Databases for PostgreSQL + +Need durable data, high availability, and automated backups? Provision **IBM Cloud Databases for PostgreSQL** and connect MCP Gateway to it. + +```bash +############################################################################### +# 1 · Provision PostgreSQL +############################################################################### +# Choose a plan: standard (shared) or enterprise (dedicated). For small +# workloads start with: standard / 1 member / 4 GB RAM. +ibmcloud resource service-instance-create mcpgw-db \ + databases-for-postgresql standard $IBMCLOUD_REGION + +############################################################################### +# 2 · Create service credentials +############################################################################### +ibmcloud resource service-key-create mcpgw-db-creds Administrator \ + --instance-name mcpgw-db + +############################################################################### +# 3 · Retrieve credentials & craft DATABASE_URL +############################################################################### +creds_json=$(ibmcloud resource service-key mcpgw-db-creds --output json) +host=$(echo "$creds_json" | jq -r '.[0].credentials.connection.postgres.hosts[0].hostname') +port=$(echo "$creds_json" | jq -r '.[0].credentials.connection.postgres.hosts[0].port') +user=$(echo "$creds_json" | jq -r '.[0].credentials.connection.postgres.authentication.username') +pass=$(echo "$creds_json" | jq -r '.[0].credentials.connection.postgres.authentication.password') +db=$(echo "$creds_json" | jq -r '.[0].credentials.connection.postgres.database') + +DATABASE_URL="postgresql://${user}:${pass}@${host}:${port}/${db}?sslmode=require" + +############################################################################### +# 4 · Store DATABASE_URL as a Code Engine secret +############################################################################### +ibmcloud ce secret create --name mcpgw-db-url \ + --from-literal DATABASE_URL="$DATABASE_URL" + +############################################################################### +# 5 · Mount the secret into the application +############################################################################### +ibmcloud ce application update --name "$IBMCLOUD_CODE_ENGINE_APP" \ + --env-from-secret mcpgw-db-url +``` + +### Choosing the right PostgreSQL size + +| Workload profile | Suggested plan | Members × RAM | Notes | +| ---------------- | -------------- | ------------- | ----------------------------------------------------- | +| **Dev / PoC** | `standard` | 1 × 4 GB | Cheapest; no HA; easy to scale later | +| **Prod small** | `standard` | 2 × 8 GB | Two members enable HA & automatic fail-over | +| **Prod heavy** | `enterprise` | 3 × 16 GB | Dedicated bare-metal; highest performance & isolation | + +Scale up at any time with: + +```bash +ibmcloud cdb deployment-scaling-set mcpgw-db \ + --members 3 --memory-gb 16 +``` + +The gateway will reconnect transparently because the host name remains stable. + +--- + +### Local SQLite vs. Managed PostgreSQL + +| Aspect | Local SQLite (`sqlite:////tmp/mcp.db`) | Managed PostgreSQL | +| --------------- | --------------------------------------- | -------------------- | +| Persistence | **None** – lost on restarts / scale-out | Durable & backed-up | +| Concurrency | Single-writer lock | Multiple writers | +| Scale-out ready | No - state is per-pod | Yes | +| Best for | Unit tests, CI pipelines | Staging & production | + +For production workloads you **must** switch to a managed database or mount a persistent file system. + +--- + +## 8 · Adding IBM Cloud Databases for Redis (optional cache layer) + +Need a high-performance shared cache? Provision **IBM Cloud Databases for Redis** +and point MCP Gateway at it. + +```bash +############################################################################### +# 1 · Provision Redis +############################################################################### +# Choose a plan: standard (shared) or enterprise (dedicated). +ibmcloud resource service-instance-create mcpgw-redis \ + databases-for-redis standard $IBMCLOUD_REGION + +############################################################################### +# 2 · Create service credentials +############################################################################### +ibmcloud resource service-key-create mcpgw-redis-creds Administrator \ + --instance-name mcpgw-redis + +############################################################################### +# 3 · Retrieve credentials & craft REDIS_URL +############################################################################### +creds_json=$(ibmcloud resource service-key mcpgw-redis-creds --output json) +host=$(echo "$creds_json" | jq -r '.[0].credentials.connection.rediss.hosts[0].hostname') +port=$(echo "$creds_json" | jq -r '.[0].credentials.connection.rediss.hosts[0].port') +pass=$(echo "$creds_json" | jq -r '.[0].credentials.connection.rediss.authentication.password') + +REDIS_URL="rediss://:${pass}@${host}:${port}/0" # rediss = TLS-secured Redis + +############################################################################### +# 4 · Store REDIS_URL as a Code Engine secret +############################################################################### +ibmcloud ce secret create --name mcpgw-redis-url \ + --from-literal REDIS_URL="$REDIS_URL" + +############################################################################### +# 5 · Mount the secret and switch cache backend +############################################################################### +ibmcloud ce application update --name "$IBMCLOUD_CODE_ENGINE_APP" \ + --env-from-secret mcpgw-redis-url \ + --env CACHE_TYPE=redis +```` + +### Choosing the right Redis size + +| Use-case | Plan | Memory | Notes | +| ---------------- | ------------ | ------ | --------------------------------- | +| Dev / CI | `standard` | 256 MB | Minimum footprint, single member | +| Small production | `standard` | 1 GB | Two-member HA cluster | +| High-throughput | `enterprise` | ≥4 GB | Dedicated nodes, persistence, AOF | + +Scale later with: + +```bash +ibmcloud cdb deployment-scaling-set mcpgw-redis --memory-gb 4 +``` + +Once redeployed, the gateway will use Redis for request-level caching, +reducing latency and database load. + +--- + +## 9. Gunicorn configuration (optional tuning) + +The container starts `gunicorn` with the settings defined in **`gunicorn.conf.py`** found at the project root. +If you need to change worker counts, ports, or time-outs, edit this file **before** you build the image (`make podman` or `make docker`). The settings are baked into the container at build time. + +```python +# -*- coding: utf-8 -*- +""" +Gunicorn configuration +Docs: https://docs.gunicorn.org/en/stable/settings.html +""" + +# Network interface / port ────────────────────────────────────────────── +bind = "0.0.0.0:4444" # Listen on all interfaces, port 4444 + +# Worker processes ────────────────────────────────────────────────────── +workers = 8 # Rule of thumb: 2–4 × NUM_CPU_CORES + +# Request/worker life-cycle ───────────────────────────────────────────── +timeout = 600 # Kill a worker after 600 s of no response +max_requests = 10000 # Restart worker after N requests +max_requests_jitter = 100 # Add randomness to avoid synchronized restarts + +# Logging & verbosity ─────────────────────────────────────────────────── +loglevel = "info" # "debug", "info", "warning", "error", "critical" + +# Optimisations ───────────────────────────────────────────────────────── +preload_app = True # Load app code once in parent, fork workers (saves RAM) +reuse_port = True # SO_REUSEPORT for quicker restarts + +# Alternative worker models (uncomment ONE and install extras) ---------- +# worker_class = "gevent" # pip install "gunicorn[gevent]" +# worker_class = "eventlet" # pip install "gunicorn[eventlet]" +# worker_class = "tornado" # pip install "gunicorn[tornado]" +# threads = 2 # If using the 'sync' worker with threads + +# TLS certificates (if you terminate HTTPS inside the container) +# certfile = 'certs/cert.pem' +# keyfile = 'certs/key.pem' + +# Server hooks (logging examples) ─────────────────────────────────────── +def when_ready(server): + server.log.info("Server is ready. Spawning workers") + +def post_fork(server, worker): + server.log.info("Worker spawned (pid: %s)", worker.pid) + +def worker_exit(server, worker): + server.log.info("Worker exit (pid: %s)", worker.pid) +``` + +**Typical tweaks** + +| Scenario | Setting(s) to adjust | +| ------------------------------------- | ----------------------------------------------- | +| High-latency model calls → time-outs | `timeout` (e.g. 1200 s) | +| CPU-bound workload on 4-core instance | `workers = 8` → `workers = 16` | +| Memory-limited instance | Reduce `workers` or disable `preload_app` | +| Websocket / async traffic | Switch `worker_class` to `gevent` or `eventlet` | + +After changing the file, rebuild and redeploy: + +```bash +make podman ibmcloud-tag ibmcloud-push ibmcloud-deploy +``` diff --git a/docs/docs/deployment/index.md b/docs/docs/deployment/index.md new file mode 100644 index 0000000000..bd02d1ce2e --- /dev/null +++ b/docs/docs/deployment/index.md @@ -0,0 +1,53 @@ +# Deployment Overview + +This section explains how to deploy MCP Gateway in various environments — from local development to cloud-native platforms like Kubernetes, IBM Code Engine, AWS, and Azure. + +--- + +## 🗺 Deployment Options + +MCP Gateway supports multiple deployment strategies: + +| Method | Description | +|--------|-------------| +| [Local](local.md) | Run directly on your dev machine using `make` or `uvicorn` | +| [Container](container.md) | Package and run as a container using Docker or Podman | +| [Kubernetes / OpenShift](kubernetes.md) | Deploy to any K8s-compliant platform using a Helm chart or manifests | +| [IBM Code Engine](ibm-code-engine.md) | Run in serverless containers on IBM Cloud | +| [AWS](aws.md) | Deploy to ECS, EKS, or EC2 instances | +| [Azure](azure.md) | Run using Azure Container Apps, Web Apps, or AKS | + +--- + +## 🛠 Runtime Configuration + +MCP Gateway loads configuration from: + +- `.env` file (in project root or mounted at `/app/.env`) +- Environment variables (overrides `.env`) +- CLI flags (e.g., via `run.sh`) + +--- + +## 🧪 Health Checks + +All deployments should expose: + +```bash +GET /health +``` + +This returns basic system latency metrics and can be used with cloud provider readiness probes. + +--- + +## 📦 Container Basics + +The default container image: + +* Uses the Red Hat Universal Base image running as a non-root user +* Exposes port `4444` +* Runs `gunicorn` with Uvicorn workers +* Uses `.env` for all settings + +> For Kubernetes, you can mount a ConfigMap or Secret as `.env`. diff --git a/docs/docs/deployment/kubernetes.md b/docs/docs/deployment/kubernetes.md new file mode 100644 index 0000000000..9e9e6bd100 --- /dev/null +++ b/docs/docs/deployment/kubernetes.md @@ -0,0 +1,126 @@ +# Kubernetes / OpenShift Deployment + +You can deploy MCP Gateway to any K8s-compliant platform — including vanilla Kubernetes, OpenShift, and managed clouds like GKE, AKS, and EKS. + +--- + +## 🚀 Quick Start with Manifest (YAML) + +A basic Kubernetes deployment might look like: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcpgateway +spec: + replicas: 1 + selector: + matchLabels: + app: mcpgateway + template: + metadata: + labels: + app: mcpgateway + spec: + containers: + - name: gateway + image: ghcr.io/YOUR_ORG/mcpgateway:latest + ports: + - containerPort: 4444 + envFrom: + - configMapRef: + name: mcpgateway-env + volumeMounts: + - mountPath: /app/.env + name: env-volume + subPath: .env + volumes: + - name: env-volume + configMap: + name: mcpgateway-env +--- +apiVersion: v1 +kind: Service +metadata: + name: mcpgateway +spec: + selector: + app: mcpgateway + ports: + - port: 80 + targetPort: 4444 +``` + +> Replace `ghcr.io/YOUR_ORG/mcpgateway` with your built image. + +--- + +## 🔐 TLS & Ingress + +You can add: + +* Cert-manager with TLS secrets +* An Ingress resource that routes to `/admin`, `/tools`, etc. + +Example Ingress snippet: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mcpgateway + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + rules: + - host: gateway.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: mcpgateway + port: + number: 80 + tls: + - hosts: + - gateway.example.com + secretName: mcpgateway-tls +``` + +--- + +## 📦 Configuration via ConfigMap + +You can load your `.env` as a ConfigMap: + +```bash +kubectl create configmap mcpgateway-env --from-env-file=.env +``` + +> Make sure it includes `JWT_SECRET_KEY`, `AUTH_REQUIRED`, etc. + +--- + +## 💡 OpenShift Considerations + +* Use `Route` instead of Ingress +* You may need to run the container as an unprivileged user +* Set `SECURITY_CONTEXT_RUNASUSER` if needed + +--- + +## 🧪 Health Check Probes + +```yaml +livenessProbe: + httpGet: + path: /health + port: 4444 + initialDelaySeconds: 10 + periodSeconds: 15 +``` + +--- diff --git a/docs/docs/deployment/local.md b/docs/docs/deployment/local.md new file mode 100644 index 0000000000..6722ae97d5 --- /dev/null +++ b/docs/docs/deployment/local.md @@ -0,0 +1,67 @@ +# Local Deployment + +This guide walks you through running MCP Gateway on your local machine using a virtual environment or directly via Python. + +--- + +## 🚀 One-Liner Setup + +The easiest way to start the server in development mode: + +```bash +make venv install serve +``` + +This does the following: + +1. Creates a `.venv/` virtual environment +2. Installs all dependencies (including dev tools) +3. Launches **Gunicorn** on `http://localhost:4444` + +--- + +## 🧪 Development Mode with Live Reload + +If you want auto-reload on code changes: + +```bash +make run # or: +./run.sh --reload --log debug +``` + +> Ensure your `.env` file includes: +> +> ```env +> DEV_MODE=true +> RELOAD=true +> DEBUG=true +> ``` + +--- + +## 🧪 Health Test + +```bash +curl http://localhost:4444/health +``` + +Expected output: + +```json +{"status": "healthy"} +``` + +--- + +## 🔐 Admin UI + +Visit [http://localhost:4444/admin](http://localhost:4444/admin) and login using your `BASIC_AUTH_USER` and `BASIC_AUTH_PASSWORD` from `.env`. + +--- + +## 🔁 Quick JWT Setup + +```bash +export MCPGATEWAY_BEARER_TOKEN=$(python -m mcpgateway.utils.create_jwt_token -u admin) +curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools +``` diff --git a/docs/docs/development/.pages b/docs/docs/development/.pages new file mode 100644 index 0000000000..258d916b9a --- /dev/null +++ b/docs/docs/development/.pages @@ -0,0 +1,5 @@ +title: Development +nav: + - index.md + - building.md + - packaging.md diff --git a/docs/docs/development/building.md b/docs/docs/development/building.md new file mode 100644 index 0000000000..1849d7dce5 --- /dev/null +++ b/docs/docs/development/building.md @@ -0,0 +1,79 @@ +# Building Locally + +Follow these instructions to set up your development environment, build the gateway from source, and run it interactively. + +--- + +## 🧩 Prerequisites + +- Python **≥ 3.10** +- `make` +- (Optional) Docker or Podman for container builds + +--- + +## 🔧 One-Liner Setup (Recommended) + +```bash +make venv install serve +``` + +This will: + +1. Create a virtual environment in `.venv/` +2. Install Python dependencies including dev extras +3. Run the gateway using Gunicorn + +--- + +## 🐍 Manual Python Setup + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -e ".[dev]" +``` + +This installs: + +* Core app dependencies +* Dev tools (`ruff`, `black`, `mypy`, etc.) +* Test runners (`pytest`, `coverage`) + +--- + +## 🚀 Running the App + +You can run the gateway with: + +```bash +make serve # production-mode Gunicorn (http://localhost:4444) +make run # dev-mode Uvicorn (reloads on change) +./run.sh --reload # same as above, with CLI flags +``` + +Use `make run` or `./run.sh` during development for auto-reload. + +--- + +## 🔄 Live Reload Tips + +Ensure `RELOAD=true` and `DEV_MODE=true` are set in your `.env` during development. + +Also set: + +```env +DEBUG=true +LOG_LEVEL=debug +``` + +--- + +## 🧪 Test It + +```bash +curl http://localhost:4444/health +curl http://localhost:4444/tools +``` + +You should see `[]` or registered tools (once added). diff --git a/docs/docs/development/index.md b/docs/docs/development/index.md new file mode 100644 index 0000000000..68562bede5 --- /dev/null +++ b/docs/docs/development/index.md @@ -0,0 +1,47 @@ +# Development + +This section is for developers and contributors working on MCP Gateway itself — whether you're adding features, fixing bugs, or building custom integrations. + +--- + +## 🧰 What You'll Find Here + +| Page | Description | +|------|-------------| +| [Building Locally](building.md) | How to install dependencies, set up a virtual environment, and run the gateway | +| [Packaging](packaging.md) | How to build a release, container image, or prebuilt binary | + +--- + +## 🛠 Developer Environment + +MCP Gateway is built with: + +- **Python 3.10+** +- **FastAPI** +- **Pydantic Settings** +- **SQLAlchemy (async)** +- **HTMX + Alpine.js + Tailwind (for UI)** + +Development tools include: + +- `ruff`, `mypy`, `black`, `isort` for linting and formatting +- `pytest` and `httpx` for tests +- `uvicorn` and `gunicorn` for serving the app + +--- + +## 💡 Development Philosophy + +This project prioritizes: + +- **Strict API consistency** with the MCP protocol +- **Composable internals** (service modules, dependency injection) +- **Production-first** design (SSL, metrics, rate limits, etc.) +- **Dev-friendly tools** (Makefile, pre-commit hooks, typed config) + +--- + +## 🚧 Contribution Guidelines + +Please see [`DEVELOPING.md`](https://github.com/your-org/your-repo/blob/main/DEVELOPING.md) for coding standards, commit conventions, and review workflow. diff --git a/docs/docs/development/packaging.md b/docs/docs/development/packaging.md new file mode 100644 index 0000000000..c3b3c1993a --- /dev/null +++ b/docs/docs/development/packaging.md @@ -0,0 +1,83 @@ +# Packaging & Distribution + +This guide covers how to package MCP Gateway for deployment in various environments, including building production containers and generating releases. + +--- + +## 📦 Production Container (Podman or Docker) + +Build an OCI-compliant container image using: + +```bash +make podman +podman build -t mcpgateway:latest -f Containerfile . +``` + +Or with Docker (if Podman is not available): + +```bash +make docker +# or manually +docker build -t mcpgateway:latest -f Containerfile . +``` + +A lite image is also available for use in production, see `Containerfile.lite` + +--- + +## 🔐 Run with TLS (self-signed) + +```bash +make podman-run-ssl +``` + +This uses self-signed certs from `./certs/` and runs HTTPS on port `4444`. + +--- + +## 🛠 Container Run (HTTP) + +```bash +make podman-run +``` + +This runs the container without TLS on port `4444`. + +--- + +## 📝 Versioning + +MCP Gateway uses semantic versioning (`MAJOR.MINOR.PATCH`) and the version is defined in: + +```python +mcpgateway/__init__.py +``` + +You can bump the version manually or automate it via Git tags or CI/CD. + +--- + +## 📁 Release Artifacts + +If you need to ship ZIPs, wheels, or a full binary: + +```bash +python -m build +``` + +Outputs will be under `dist/`. You can then: + +* Push to PyPI (internal or public) +* Upload to GitHub Releases +* Package in a `.deb`, `.rpm`, etc. + +--- + +## 📂 What's in the Container? + +A typical image includes: + +* Gunicorn running with `mcpgateway.main:app` +* All code, static files, and compiled assets + +> You can override settings using environment variables at runtime. diff --git a/docs/docs/images/mcpgateway.gif b/docs/docs/images/mcpgateway.gif new file mode 100755 index 0000000000000000000000000000000000000000..83a5de37339bf34000cb666d51aa65f4ba9755d5 GIT binary patch literal 1552478 zcmeFXRZtvE6sS4F3~mDig1cLA9mwDsAhgFSr{W0MODi)+iPo2zRZTibivJ9}UE4i3H@9ULAX9DP4L zIX^i&KmC4j_MhjMCznqrKcD}n=RfW)e_a3PkDvd;pI0~kd3AmJpVv2c|I3@(`~SPQ zw|D>ByZ^=g{r#_phyUrrum6+ZzkmPvzj*xf=kd?u|MKsjKTm%i|NiIWF~%2#QtoXr#S`o2BcRyAJ)3&){RZLj`PrkuuU zHq~CURHap>TA*&N=CD+2Hr?5<)#d(esz9x)ai`b+ z_WRCsSJT%4I0%&uu63#d7hqZ+hC# zma28?%x8N#&exi4rVHQnc3y6Ed!6sj_ICaJiVVl4(?E1zf160-vY11BzCBqeQ!CQw z>$yK)Z*%@S*Vp^&=iV?Xo=&qL@#pq*sm@{^RTU1r`bLsWf2#-vUE#tUWLo7wXjlC1 zK+}hPQFzExN7Y^!p2+E5I7IPuIq-T=)Iwbps>KjQuuY$ej_zUzM}KeEy$_&Ktv-n3 z?p!Yg0}9D4z=SgNHbLl?R}1)L&}LW+DX|s+L}qYOx&Z!ZfbAYZTxk(1Vv2}8coI*Nh>$y+7g4eAc&kZI5_=97x-troBm z7gxnckU5=}>XGM%KDf?sfz6sIDEY3SKF=+vL-S!ki*4JCEuTkc_n#turFd`K2lLJ&@v* z@6}qk5QI@}dG62$Mi7mI&|q^dtFv+K?vR0t!-G*zLq{KW9W#*Bzx*8Va>)B4_Sl#C zXYCm3`vnsG(&fr|fivK{tjLE+9$E6_y6?kiuDOjp`^N1s;{EPT_1vPs4czEBW4H+2p;KW_U=-=BVY*W~c9k(MI5U}^zP#dDX1`5W}$t0gTZ zo_kyDuM^c}WYfwXGQ{0~c~g0R#X8H=ASitYq!b)QqG6Njx6@=L7Y$PdX7nvxCV}Zd z)C6hiCHXihujSBO=*^?{Fli;ZHs;}{^_0!n6L3Hf0ds`dQL#qDpW}SLLW31!wRkP` zO&$3CWn!7~Z0(XbGlp>*74 zSR`KI|BOz}`+Bi((8CpSm)}PD&y>;r`c1MR4A4X;XOvMkU|?nOpw~>$8zc4%#lZU# zvRapMlfRmb>pu%f7sA4#PL?j*e#>UOywjzdrc5xa%J7GTSqa9Tg9i{m+cc(CJSNZ9 z92?0DnyKLRo19L8e*!|OVQfU7OVDAh^rY!uK&%tVHk2c&0+f}qfG)U#2zr(%ww*#x zcCKw^at^1Jooyi&Z>-19h{&?Cia8?^mxr5ztAr{j(am(=PBXnYYI^~cFP==qN|2Ac z&nAOXKt^}ps8KD%Y*p(jW5`>&FR8OZn)QZ%lLOGIUSK`cM#^D7r5F+v1MWh|d%}N{ z*uZ;snoQqH2IPTtLOgq+9{_;>P5qgNMz>7QIoCdFdAGFnS;H7grZ zV9DrMwI=YTNyT~b^hc_)8V(=5BI}EonV@w2=ua*5RDCF-E>=lE9kIOYyUp()Zh*#S z<8*8J_j8LAmF)vc(YKp7=c(n!5ZNguw83(6GszXbI4p1a_Ks3HRgT#hty*t3CxH22 z?%cBDrBBPs=2^3_u{!Up85cU5>#KBaicvoXo{R}UHJ06+Rbdm-8*3SMcLZ>Pvl(<< z=<-@G?d<&oV5Kj{P6@XNo$sa3UW1cj=pLQMy+N!HPYiY#hZ_BeUN*P{{zm)=o*=*@ z&G91UTEp8S8qp27AKHku4uasmAU6#yU<$2MWehc?&%O=-Xa{o{#fH|PyW($~;I|sJ zDZ+>J)n7T{zAK%;j%ZfaH@C(vwIec$pTy3Ap|Wu&_rm!orc{lQ=}BrQ5?i0e2kz{H zo;v%{ZDH0g!tKp?Jk3Ib_TKLZb3rKJy0O^?dSF120j|N+Nh-y(I};Y>YW@6-!XMa6 z3|U4UT??qK>u1|zDjF(-pHINCdJgL@Vs=AiRP7L1= zrwZiJ*x0lwc8u1m!|GfXd`3~NzIrO(g%;NGW;WSyTzn#Z&OJo=&gF$2!R)%v4TjU3 z`fL3hddmxP$dswFf5}zsNqu_)xx4Czca%HmRz^LC7xq4FTczLn+J;`vxo(A4Q%PlF8g@v=GTh{ssxxI3R#V_*yC=zcdh?zB{QHHT zU5*tb`lL_fhV4DFKzp2Ux7jj&<1|Xkbd5~}nNQ^D{fC*RI#e~FhWyH!Vv>08#>aE6 zjm8|J`mR3PfM_!a&sGQyWSa`O$W zt$R|sz*Uyclb$;5ubfsB;)7B71p}*Fv1Lc|&k=J2aX7m&=u%UU5a-56Cvv z?&;rMzgGU8^R-b)GiP<|^DR`PMfS7*`7f;OTH1%a!13Fv|N z%S@Szf@JOy%49H<(kTFf9we#m1}}RKk|uajWe->TcopNtgeB^`0YA_nXyy0 zkaT}~zX`}&RxuERuV@Cur5gI)Fc|%MU2<2{NfLLfYI*@Durr7SLl$xM0k)YBY? zN%DgPkGplT@HcryM(VzLYY)I4|HRw%hJZb4^qnvocJe;E(=MZBg%UT3J2QE+_q1Hp zh=3n+2g-#PDDTh~4UDG4hgg9IV}7w{U|c(zT_}lGdX}cIiNDRC|Opp8CvJ`4j zaw!F;Nic{V2#ZKTJo4c-l+E!iN6lOAKOb9#P2I`fX|Dib?NIo*GlpfLop|_hm2x7X*j>HFf3coCO$5;e=h8QsryHv+dE(qf{%X^O0v)|dt6gj~ypDKQ5 zp~=K%SAW>K2_k9nZkEl$V+tl6j5$`cV?hrLVGF9T$;l$iuI6A2i*Tz{vFX&YZZdK! zO&4gB%`K*gF4b{-F!JBa^np3=n;!y`Hm(jw-GZM6eRdubBONh|9FA0E5W$Jt+y_ zX6_;dkwCYnX_=x9soE-;-K3|s2s`Bc3xirLaKxWT44_A-QQYQ=$0lR(3!oB;S6{*a z8*HS?hB;W#DkPNit&06Pk%c*Xr#oA6)3icPbkN@+RG{Q{M=_Mr@?-L zbdwkW{ZYOKohUXp2)tkSr^KUi)Ay7!lWkEY|3k>Sk;DbKSgkZZvldm$%;EW`Tv%fW z(mldoKMA3SRas^buW61b3kD^;l%Z@ zW(V^99Pse<@L|Kv;xwr-&MEo)t2M>C5E@;(`Jn{o zi}KoITU4DF7SKJ!xVs^~NcFrtZY*B&FJghJPpqU)fr^bA)q{9J66m#-b0!PGZ&tu- zk^i}&-t?{#_|VxI&-N+fJ9S(Y1DVPNcF)9=6@odRE6eygos2Yk( z&?mxN`)os!06g#Mt3x!Mw_>Xyl%^5FE_!Bus@(OZS4-BG6TOskaJ%5@nk~u;IoXnkf!b@ticMBi^lH2;(t^VsyM@<2rCupVKv-35j>H0 z$*J>4&su9hl}?+p`ZTZ22qJ!jo$76UFGB1Bp=~-d7BurxW>hIw}K5ot4$%n_k zL<)iQr7q+43FDBUDYWgO>iXHPZ{xgau&b?>o4i@m_Mmm>?CXTF;GnK@yzxOiF#KZZ zN_+z1k#1WL86FN&YuCfS0uWsx-Ek+|@TOnZJ03Fn#eQJWON-YwYGCMLw!i}tLp(6w z=1>-w{qcyyE$Q4vOX%XNseBsRnVD*WqLBTPz0F$_ozppgM>(`Vj0xoiqeqB?gyPvVSn!LsV?e5eFhsx!k{d>`wub0M_*6snd(JqA?hqHwJ~ zrLy^c-nNf7Uoeqi@4C8`tt0MN?G~?imM~@_rQ&$J^5LoK05OhDq&plQ^KNI2YLj@y z6nPgkL9*hL4wi`DZnD~n40c_uAvFj~{bsb3BJlSIy>2WW8+|o*w=_^-XCFE3|PAZ<&{7TFq>G zEOW?O_%O9xo9f`jO(cam^T}k5 z^=|W#jcKCJ_;s#JMOlyJTUDq7@@YEmo@Na^ zbbeB?c?!cBjIm$j*yt(zoHJhD>-L7WGpcMCwpS%u_m$186^L;t3Y@|e=y|UHs?gJu z;Cvj!%0y5IQTo$-y{(VJL$cY0ymA;G-g1E;e~k+Do0Xa|u*x6Tmg^?=j4AX@)k&RU zUA9`!31`ZRwwo<>^fGXaRP;3L*GqA&%-Cod7Yh^)i}ru7pKG6VJ=BR^3`m>!YSZ{O zj81uEjt=Mork9V&OPWNR^7(W`_G6aq%>kBDJ1H#@i5bJof*m%@12?mA0)~s5C(Bjl zV+txR4W-;^9cMi$lJ>$h*Qo)`yIQpUR5hmsV3=-*L_1`#1s7r}qa%z_EaxHpE$;n) zS>q(y7kzXX@68TGlco+QMOm=#QuN3h=i`NAzHCb4X0_CJ%#!cr!LBVA2NdGYjoT_k zWZy5x8kNjO8In-yez!$KW@ioC-?_iuoMmrM(adYVIgRhtL%xVd4<4uda$)D7F7~eq z{tbYJEmnhm)PX-m0N3?cw}0SZshK?tX6s}&OW9VvBp|wz``TukxE4?_xf+(;8Y2;M zQiJOvPuoEJi-R%vN3%o5alBWptD`XY%RuWtQZB%- zlQM6M!)*9Z=1d;;_5Kv16_LAErMJA{=T_o znV;R7J?lgD*qgcVd^gvnNSD!Ry-CP^MNKmMR2beD;`IFqGVzM}0OKeBfMRgo6K5B+ zV$&H<>zTlr{{0T|Q9iE%X3fu^%@moUg*=ghaD@A+MAFdK-XF?n#=>~Q@$R$%h${cd0IKMY#a-|h#aiF8VZrhjg> zv>DpuKLc2ghrkrm-sT%eKCuOu?&CWQExz$tOrKa@FP?rWKW8a@wf^#O_cAdQOT9^) zJDKVZSEL&|26v%$0ve04{DW_CuJ%mm_s#i)RLca~WmG1;3t@Q?-_xA*5pVS;xiHTn1H_!VPf`__ys5ox+I2yHrkh6N-RqdGz)Q#Mh7r4O<~gyM~;2t4Z|w8nvl;S&R) z*%9o5;SYHZoMA807u%j&srTA`G=E5vjUku64$H{boskwQ!P8M8&s+W^$=e4;4au`m z83N12Ya%8jC2~MQS~8d{?OJk#3PNucUfQ+4RicX&(pF)s5aQ(OJtTQec@O1Rz<($I zMUEbEyDS1hOealHCCntj>E>tLD=>@FA*J}|Iw^WLh#7)r9OlJ)NkJLUmR3U1COETXE^Jlp+Y4cFf`~(RBl@t8iBL7QzNFFSk#ARqC@r%agV+xZ zSY;XdfG$R7s*%{~P@D-%;Sc{ZMB7cic_nR%HC6cZ8`gkG%0n@cH}bE3-EkgZi=s1b z+{Lu}m>`#f{44M^+8uxlNmA>LV|*Ccp!5zKxMsG@P1?Mw-|^>nMITWZ5)LKNtUV58 zNs1DVWtb>Ikk&V|+NJmWHeynmDg`~ARP+!d$>gpdn~g3u#%=oarFMelQ`%svq4_W& zZdi=QF%~id;yr1W57of#=)C2!bTJwQ&xTd~Nd>&|W)cf(txs|eb!K@9p^hP~{p4)v zZGsF^t>s^qH0F^>C1Oct5I_hdExZtA^}MIWpKY(i#sA?cqiS|PqW0gHX|?bthl9A# zfj`qHBD@RrW0R@9il+PW`Qyz4ZAeMK$>(2o@jfWBJLt#PyT6$-SO$gonohjZr_osAN{Ori&0kyqfVTNpJ%d znjjL5GC4du^d*qq9W-m_irz|PrbwPe3)0n)V+>r1OVw`9P%rinD7R8j$4bSao^vUG zYbbL_EQw;!VifhB!V4ZRJoUiw*Xt?JkLl`5h-``^@-Y5Del?sSH0|*rgtJ=c=ons>}HAS0C{Yi=-7N})`%^u@Mjeb-^H{du-%vAmn3h94!uof4b=6M z#X#^{0A?(lLR?4g>oyXN&=61^F$BjnEDjLhmjP3v_?XbB<Cexa7{lVPk{4tnwuEOy91QhIxyDyuAM<^)QMV=dWK;SX4#JTGU{I4V z>h%&igiS(%d5}O7hm+#U4FD*A8h{T+V#s=hV^=Q;67ty)$&hFoNFc$92s@dzSS`2R zCVDzBnG%`%+x&VYvpfS5?dcbiCbSZL*9cCau*B3a_VxoczX|ljX#T98DB1P!_;)MC zxWZgZY8l!tjh6rr^HAG@nq?LvADi3(^&+ez?|bTZiek9~`C0}}m>!LwN&@ki>S_>N zRz?RAbI6_U7U^tyBw^6Ay3*)3>TG`BgEF}Bm<$OjaDEwpW;jH)+8nLmVolm>I3}{% zk`(D;yJIlLr9ze#S5|5#+-o%FyV_QS?&_c*X}p-Z+FpmvB$FLrTyoT6K49zWVkc>` zxw_ifHR}4_r`Kd!prZ`-qDDR7yc)IV-94`G-ZP`u^jKu=^L!+)cfk?>=gAX2>CV!d zkTjH3LeUrZccJg7r1?$e8sbF3?ZZm%?$N?|F+RP6-PMTsVR5v;_Y${|`(6vw!`i?< zbayzeWQ*CeKC+9*%#;9Oi6Oc^NEqcF!2-2fhja^I_)|w3q*;c|7!1>4c*H0`t%eF&_99&yvu;sd z`A5)q5^EcCeq&yR_lS4Wzc%JSVt5y$OF78C++2uO^e!dsb5IoB{E`&qUCtuqsA{me zm^J2IDct9%;kUU|gyB=IAmyZ;wYglS=u@lL=cM1YxzZHnQ*YNmHRR&8Iy4ENLL;NZ zpSUe%*2Blba$1td?v3qI^li=PbGf6~0@PiuqY+TrsIBI1ASdt9eEUQlsVsr`#hvO2 zZ(QUP|8AZr`t_{LvcE$~!Y3H`k?kL&fSBt&BCBNleV02ME^dLW10ji@1DQfGU|T|H zU*F(HsQmB|-QFi0=xj^kA;I^i+I}66lyOKrB6>&dn|awKjg6zdN*^S8!yGUfy+f5I zw|(>~`g2|48!b$vJ~w7Y*F3cUI#|>Hq!d43K#?FX^919lh2zmRmDa0LbmuHjM1&WY zB8`v_#v}f7W8NeDC&ZbiUFPMl1yKLZE_K1F?bq4GqJH1bF0(IVpWx76m)8jv)Qo(} zrd=zobV(#Bz-x0DI5+xNC7Sd!Q473@&E?T zF?1Urc6_BSShRr~X-htaAx~F&I2V7Z!S~l9gvT5fSI3EYyA5BV5bnKz4y}Qodrt^+ zPQu@2OC~=nUM2p;|2Och3w4hiYQ+wEL+%uCMh|4^j@C_weJF6C zejRzQlT66m4*|y#-E|VnOXfEZj^0Xu%Mpidqy&dj<0P<-^$xORHqcoTQ+?XNbS9Mp zY$^~1i}4@IO=IPtPWz%0@8{La_hOhI%GaF-a9aW-^MzW~^)^q4x#>_G{DsF{iJ_(u zt{>zwTDrx{<)-3?>vSN`AZVYBSPhjthE^T+f+S|TfDE-fvW~iOdRVNz_e-_hR2r^A zkUaO_LOlw3M@c-Tn_3UK1h!`Bh5q~PCHxY6M7bD9{a*14t#%;q=@}tY& zqzn^=w{TzC2*z4mz}`G5_{k|?t;=IL;K@bE$=6GZrFW{nNL0xmYrYzN9|*sy6&@w& zCpcuxaKux38dd(@7rr-Ihy;aQGvEeYqW1M2>cB%vS* zjw8~a{{So`6?qe4T+&BLFJOJRK{t$D*#kHz|2)Io2gRPa=bO6lMoe71E=ilIyX(C2 znnq8~Vl#zP93-tLhz?$>$6UQB%NGBYA5-tGTKz&L)mi;BVs3pzlRH5TB|l4&fc@ra ziuf9ePK1|AJc?yEF*T^s1{Tv#(0sh>40}7K?hU_L)-tK@`4cqM3eo7RCpn^?qF(tz z6Up0MHm_#jS+NHi_#ofMZhpOO7vcBT;<^JSP_y(J74LvF^?G%d?frkTgB?v;R5 zM$Hs7Jc%wO1&UB>gpOy{_ZLB7VISZY+KPoxr786{0>N__dvex}L+$cQ3~v&by%<+U zQXITx13UrTt_xWupRZ&WYcI6J;->tW#Phkyn{ahjs+XmwbYiP}&y=Npt!w|~6Df;X zbSPc)64IedOk~)B@z^d7JE%L%Wb~xZ6AGwe4|eg)_WiTfX57#nr)TG2(MIR*75341 zO2ajMqlP=W#F`kxX2#WWtA|UY$M)QtRIS&0#|8+JwWrj!E7zc(=tOku(z2|13a{I&C7aFSoWKVg2UkjXJP>NhQPDNdiOGZI>gQekh-`Q zu|00|yiw#bQ@LZLpBVEYGA7>)fPqY@Zj-O4(od=|hT|G8rier>ZBA&ZwTjE8k!} zyzzy`SW3t`BgdcvDkogATf%~xE&kSnQy{(M0L_J3OGW5SEK8CuOR=#YoGU2w*;yxL zj4{nweY20ZK3m# zAZ8}J@oY05tz(ms;S+p+-@x4d_WsVd2;l1Z`)rW&Ji>&UJG=kN$Zf+EHC8%prAoH- zr(IFApIu8|QtZLBWt)=z(=fk)xB|i9H0;pg?U+kSRT4ekEmDcT@zAwyGd^`v^75Nh z=Dxk}@jC*RjC*~}dh_0ITj9;{pA?HzG$dLx_G9r0>2UCFfDjOG2(}MFlgH!{#4+>E{w#0$C>yJGy|42aEUbFwM0(pHC(O zmu*U9EhLTYLr=oH*+U8NMdp@>GV?q$E&I*@t0n+k`ox?(`0LEC)tAHZ)3%wPurr=b z^4;wsmL2|hmpc2x-87b($+vPcV{=yxPiE5@cLwpXAawml%)!#H-@|L2W}4Estz;ZC zq`%Z|&9^yPqPkDxqK=y|&YM5cW$@T42pwk5#W`kL_I|T(7~3qgU+>q`Zb7UqUG-GW zI6d*Kwyo%d>Fw?T-z+*n5p(r88?zRUTCqFE87W!5b{l++#fW~YG5gG6jomKuxwaoC z6Ax>6ysP4@;;5`=m3`ABUW^NC1^jX2D=%Rx73NXJ4xk{j@=@v z$&On5_by`*mJ3%_4eDE9^e@di`W=eSV8y9D{O<$1s!@Vp=z0x8?6pmP$ZU*#3int2 zJkf#SKeayUy4bgFNhUJ;344X?{K5CFtSG^58_l+DO4P?vY}D%}m4D|G(GA4&9zbUXWah00qMPc|siRDQpPj^rz%#*C<$I zBwwt!@B}>ny3G(5m;t|%(AC))>kaoF3|Oo;R5D<`xi@R+O@mK6%ox$78Na$Sd3lB4 zQ}w&s9fQyKbGh!WjFc(N5~5x5i0XOCzX5sNesQ_~R;WH`=QASs%XWUj?qz=W{Tzmf z>Kjy*zz@oY!bCvbi;=sIU2v9~xzfH`XKw@dL!q$pp1pE%+|1yJ?6I@zzOCZbp~9e* zpXD&X-c4NDz*~_p{w%#U(x5*!TR6g1%jlDJSQ<4#VEK_=<3DXC$E&rof3<(;MaJ!4loCr#PtY_a< zl4tM0^9?O`bz0ds`>SkC&B&&Qu&@Pu1TCeE2x!b+2X@`$G8=V@CK>IHFw+ET+3IvB zjTs(Ft=oF7)NehvY&YR%Mah*2V%h_ zk%Z4eO^?;@j9sWlY9}6%U8<26MYM>lwjxEhl}ROU_(9>wwAizax_*6y3P-=Y1 ze9yN(f_|$#-WmM6CnS*lsfxX?L!gDg+_rzFW%|=KG&&+dlixpv+dl)s!hom)I2^7l zWpMHE$SoHJEu%1T$$8x^fmSl)L}UUll&|(cAVM798-&&CBmiZ+5sRg58$)YzXPio&Uz zA)4mHZ-y9#I5EapmRx_vIF8zG#(18d-;4?TQ({bs!khj~pHSl8yP1-t9)B|>OGOFk zCBJ+;fm95f za8%|v^>9=be0b!jE{T(Hst67_tCYt|jZ6Lr8k2WOii*nqUew;$Q(0q~OOX%mdVJ(+ z9>kI4ZW*Nv3tHI;h%;I&I)e@3C@Y1BLwHA z{yqsV$lyu|eNmta7Ftx{>JwUeBl#?}{8mj$cty`7Sa{XQxlee_Ebv))-6~#6WWz2m zSY*@jzmBgh*WPE5ZI5Xw(H)m#g8&fLd1`AocqO33Ib8$rzP>y5@!{8Arjwf>iQ+l8+uU^7cJA$l9wG@A(B5n zpZ81t?E8z7yc)!P4ZR+v`Ut(5%Bj0{1KAy_?#@7Wwq`$Uf=$38@q+8n_OWx&y?9wKdRj^qtu zz_Vc=rdVu_5Ta%-p%#0*86AA*T)|5{{%*;$2lPcP+shJ37R>7LdH=eC&g-^`v zavW1{Q(Mz3p)8y_HB)-6tr?9$EZjC6(?*M}nVkq0Uay*Ivzyke{wEf`aE=)(;5^K47t}@BzUleOogheA8BhdSZitI2S^R+lw)z*kws-7a|4PO9+D5<(W9Y z#A>&flJ&7G3f6u}^lUGqeP&md<6KNhZ7*k);!xG8UCd~0uiy>lP`BY+%2{l$6zSv8 z@Ty%Z&~FsNU&!bYS^!{l$DykDQehzKV$L!FQVn`IQi}o!FR#2%XN`e_$~pBgVV2s= zHym33m~|Uc_39Kx7YyfBD;Lu{fKO-SCX<_REb19^mKXpUaj^lI9)QLYvksb)SiwR9 z3}3`JfyCfBNDbk7dTW=aTuvh~8vW+i3QK|DX?U>8gi(G5#E?T+e^ynmflQSv?_hJa zR$slt)(43JKiuf4bd(HO;d+0c%Z~L1-qd3D(gyt;z8zBy(n`F@<>Xq~Ap^5%_gzG= zUjZ;3=w+z_R&Cp+EAiy-fyDn^jPVLE&Zu68?+*hnN}6qMGB+0`YB^A$g9sdo`;!}* zFr=>-mK$RmP+wP&;8s=LPckzDloK#;&$%A$=d3>a9w69#%K%#$fEHlaXvCNR|60q{ z{JOy{tOADvqDS`GsrEvPu6eIby`$yDjiXNo*EdNXJEV&?FBm7U1Y=-*G&{~M=64+{ zy@h3UY-C;1JDbCbgP3ubIHWn9Zt{T;c0cwyUoVu}hF;yWj$l4tKY#&M&ytJ$Ff#jDDz%35JL8FQSdf!jWHd zkRZr((O#}FLrSHfM<~FS-f(YGClXePi-xB8x_CmKXQ`6p@a(o#TxxEy7Q|fq4P0R; zp@pir&H4zo{ZmiDSm=vH|L%DXt5O5ig#hw8Tp*}bLo|j@+<4}FD^v2!wMepu60gg$ zH-Y5p5f3dbgtPX|j2-P@>?{3nvt;{sFywnPVEnXkrUnz)u*@dRoJh+q@5NCkmOs)$ zxL~Uf0|6%6bq4D*0(U4!&;srW(0TD9dUqeD!j1gVFz(=7-7X)}&~xW-FE!ZOCMl2=k(HV=!A)n`CbUZR4} z3TB11J^W}^)0Hz-3C|pUqIr#!KkdHP@x-hYsf7wocdTGvdo`N9*Z0@yX>TV(@p^na zzi;}*TzlMbZ|r0KqZ2LT;wI|X)^r%KH&9%?m$l70wAt@g;@y?q(Zzwqdq@gq$WFzI zQ`6I@KRzTOwTVBU0_#RxUqq|AB}LU3k!GGq3Z|Cr&4UGh^)s z?irw*BQQU~2OdK95`XmhdA@#XG5y0hNcY=ZHTmZVC=?W+u!zp7ah3e*!=b|o!tQUQ zXR9<|*U7E^xL5PGL>AeXV=nae=*MOauM6rULx5j!;qlTxmS=pvz$JA0xbCMphby;YS`16c{!NB+W=*4gh7_3Q)LEY5q zJ>Nc=v{fd+s3@{zK9+nhfC19Fl5$i6gTw4I$S~ajWk%*bImlgxth53nkSQ2X9UiMU zImUuy9s~C)AT2UrENC%N9@%$B+xY2l(CG@e9@Za;G`h%WblJW!%0{o3A9*fB8kNde znl^b`TUo&^Jih)@h}_zVNC_^XBVe$Rf)g>8V0CV5F|465hVn4OEHiY;z)%{`{SnR4 zmuSX&XhVCiK74Jjp!K%PH1UCB!#4HKtT0200iECC02qJlD4WFLz{m7kSJFL(19yMq z!VVI;`b~G|oBa`!$)$<<@0SrsWWC1*ZtMEHL6|h>M$H-BOp1{n5d&y1(kp|GPvbCq zq99t2!sK#P@%S5!^**r%X_{4Rs3R=KjEqPGwk&KyCISc>DOVObBtI}q{8AVn9YW>Of(p)UW%%;)p6bQKgG!y%m$H2k69_G~idL1`hZf~|}MU9Fh!qjN4=o3bJc z!E|UNj5#yA_RpDcTKPaHHs#;hHe*#;}xLv#thxrcK?{ElGmiJTj(Wb+V5kQwVD1R7_XWDH!3J=3kAx8cooAvIn{(53MeC{zo2KSJIK@{F)o~9&}6PJ zSIj-wqaBN`GLQ+xG>N?1s4I>-PFZrJ1J3lbyaThG#t6nC@!CwREMVpLn&%pIEJr~~ za2~2STZ_r{fNd_PCcc6r-LukwQQjekOEH0q=6y|HT+CVxd7Lm{;K5n7zBcg1k!@@3 zz~gy#rIDiYmVM-A8u3aT4Y(<#4l&0GiC_go<^3AZwJ!Bn_oFovm5OEei$9cCQ`Bm< zkU?BV?65a*A`BfB%1?#$nJJB?67&Wi&I{Y?JsSaC_O*{G`>q%@Ab)`3`o(OIUSkjU z+rf)6GP~d}WVOUjlLB_BZ!Vym?CE3QeJJ0lBxm#iRjvjdV;W%`4;j|?yHQ~U#$*u# zlNDPiQj5y!;T_cJSjMQ~cPfNqw$~kPNVm0gr^~2##VaTH9PdT1fI}DSS(ofua!>A* z-BHQ`CefmeB~R6u#>yAP%-*4spdTF8oAOhGl*7ARXPy)38ZK_B9AU&OR{Xq0mApfb z)o{`k8jOZFP#QRt&A>A=!dJ z_E!5el&|i14Q?LUci);{F_y-@p_esvi?IE966bQEXqW2hcKv}#%jmLB#8$?%$(sD# z;-ZV~Ba31LyD_VLKX#o=`lSV=+Mjv$1{I~VFZ7s6piqGDX;7O zpPZXj%3D+qo2F2s!OZ*Y39cUm8D1mj=R=L@sttv!)Dgc&LNgojG?l>@Tco+j92s6j z?R&dSjCV&@r#($ZcRyrmS;x5@tQKpcS$0Rh=Ny^qkC>N7@rZ#9{*fURb!y0J(Ok=e2GIKv%yUyveQ`!>aUFrur)%`w$Ot~T zJSNS@h@IH6T)X$6eTaKWHKGy%{!R-fQfgccDJn$sh(fo0mwLgIX0$wtjyvDXw-rIC z1Vdp&80fAz^fZ7mVQ>L9&#dcn@~U_D;%yKm1>9Q9SMkp79(o^Q*DMk!Fde+=rb^_c ztvwEFMBbC4*TWey$=-RxhtT8ze4L^WjorFX!yQ@ z=8wu$MOY&79gZ?f+}W)TDQ${50H@o<2w9st$BThPQa(({z9hpE6Iz#Pm-*_&XdyVL zE%drg=*ibEk%$!Q1+UeZ35kec_e9%)eQXEH1mA{qu*8*H6+Td5HwL8Ld^o@Xue*&p zfVsFV&`qy0zG~Jv^q&=yssRuRLMHauM|K73AAaEJdmB8qVF0{W<8BRYy{MH?tVLrP zY>(u^qJrfJjy-gzptB9a!fznc43qIIKEelrcs7p~$Ldz0ksc~%hR#clhbLhe zCz^;{(xO%Kj`o6Cv)k=H{YrdB`z=8VCYQ?`1HFo`lypBX&fI4hUG9C6?+lE|q-i}0 z9=!Bc?6Sx-?Jol|^_pgU`8s_ebU#%=7wAhg$g#VA`eI9#ID56)%gd@HZ$L|`9X7Y; zdvWAHCl+6?4WEzIi8u1sGC1fz^)}*|-{%BCOm%h<3C-06f_)v zZ+^b7b;R@eHv#tQijYErPqW7(Js-?+e3|)p7FqKwa{6aCcgVI~bI8%ZR_{J2H7Q0$ zJ(Qw*-3l-UWW9=-{w}77wd19SjOx{k22G9-?`NQGM7}nN(#)COrNeFe9W}n0?$sSu z)iWVxz!8|7Hb2AN*;6~;gJ*Tf61Gq`KEtRf(D3U6o)iFV3P)jQ!@Z4!u#bACPo4Ct zJ~xReK7NwT-A zw|77iG2V-qm+V{X?fWX(f709kQ*z)}@4!DvBqjn$2pxQh7^H&^u_1>6VBD|GGzG4~TX|1)t51zo`GTOgGB^0My>ozx;-@cgSjKDNH4*HX($eao0qO9p)j zFBn(f^{u)|t@-t>eUw^{?psfi+Q{nLD3aQ&>f3BWNo{rYZ4F3mkN0iQOYN-n?R=Hm zJ&{_~l=}Ls@9S;fE@uB8q4fUC{(U;>1GfHySJK}^`@g-GK2+*I)RaCl=s&WMK7QAK z>?VEU*MH*HzY^Vlnk0Re)qhqb{k^LHdz18eSO58d^u>7p#k}<8TL0x&>HhKlN9?^Xnfd>~poOp5L$B`#jzMOe;=g*->mp+|(b?eu$XV<=+dw1{O!G{+= zo_u-p=h3HEzn*=2_wV7ymp`9=ef#(E=hwfVfBt{}{{ak8zyS#?(7*!`Oi;lE8EnwO z2O*45!U-v?(83Eb%uvG(IqcBG4?zr3#1Tm>(Zmx`Oi{%ZS!~h87h#N1#u;g>(Z(Bb z%u&Z3dF;{0AAt-~$RUX=(#Ru`Oj5}unQYR@C!vf|$|rOx$M%*FTo5` z%rVI<)66r`OjFG@*=*CzH{py^&N=C<)6P5b%u~-j`RvorKLHI?&_M|;)X+l_MJfUS z7-f`z4&E}LQAQs~)Y3~a&2&f$0+`fOPah>|(MdyfOVUXzwFm_W78t%yLc7TD0WBb?KVL>7|;GfE! zlwp*@1pwuRIFNW^R5x;&<&J?4TIenxV4#A3kWN}@r6FKof{id>cV@66Fkk@;U{V=? zep~W6)lxg2T-|se!}(xpqnG%2%S>r?3}pnCFKa;B^5G63U=~z%$Yu@6}mv z9mtdWj=hSLA40)_!*M@+=EWOV>G1+0_sE0?7C;w*0v50!YlR!dTqV_c;v9h9M=JZF z3gY(Efg4bQ-u3Un58p-DW9NJJ;0t=-@Wd;?K!OLp1%mgNen+39#qDS5a)?k+pZNhA zpge_epaHI8lmH$80`Co^0u0C=00OYK797NS6-ZkEBtVc0Jiq`C1ONatV33;e>Sqmn zU<4sB!GzqPYRogh)2!!^(0Pgh+@pxPS~aNvEI*9oEdtEjnn3tyE(fSkAS3+L zunxH_MIz(^I{TCZV#tsVd~s3&xBx{g;K^PYAcQ{*B0=`p$4N2Zj{?Df2Qf&HQF_V% z1X)4246p)7%2Iojyg(lrU=R?fG5}8l0tYN9wlhwXn$-kN8r6t2Ha5hKh}(c1r3L~} zBJKewAP5X1@W#U}AObKDNCXatIt#FCixgm&5P?ufAarwd96X!@Y6*nF6>fbGFhM~u zFivt_z;Lmo2>_LP4sltp3NTV25VBg811Z1^Fwz^; zjHMF*Dgl&eOAl^Qrkba}jPW8KU13+f>cge(tdIlg>BIy;0fN*_t&HLtY$sJYo7VQW z0Lv{w{&GfJxiJI+pTeh6cQ!SqE{kh70It|a?;=+<(v=|+FdRGcI*`R7P6ESQ-#2e?JH}bS1QSqarB+~CS_#+yx&^}R z7;sR7JXW0sh=2*~8(M)FI0EMMz+K0fVc=~5!vK{ZyU+#Er~Ph=^DMx(Ccv(%0imWgnrv~ukD0^zk1DT!~W z00lU>Mfk3o)va!ee9K2)hEz?p5%A%D#~5G@$Cz=L&A=NMaAWl~7ziC$fC28g&o`Ql z!iQt+@d^;Q7ElrhTaGpm?CR`;-0`9j1wv_U49U>y+5mRWEJj-hhdX5;PDj$$(QTP=F3FaY0ZJYOE1NdcAX5QU}6s9SZQ6pfWcF zdK=V+1iGPS@&{icp} z^Opav0GQVk~6h z&lnxHg9ifbr{(=y>U#h@7ob4oWvtoZ$0^x?z<1S>9qkB}d$*m*cmB7_eZQ9Nd%7nk z=;^MW^e#VT1suup^o;0Y4gg-RAXH|Hc&n8Z;?LMlmYk>I3ZeiHkRTE;yl4*Y3NPgr zg5xak^tucopbP>WU;zk#s2;ENNRCnXk8?!L^Uka8KJKLgAdWPE20>4kP_1wv@bXel zy-Fp~x=aG7ZTXDw2$2vdn$LWg4p|H*gXZX+tPgQUPWuGnc7knkCSaZz>+2Gt`~s?u zx@m3xsE&STr{eDZ>Q2M3EwCaCo)~NTkZlgbjt>maukWl4b*!yYc#Z#T>HXHw+r+KhCg-a%Kx#n3wJ1mtcWn5s4DKee^@Pw| zrlw}BQJ}bMSsq3L31S_qg<(2?%kW8hM$a6VD~ouk9y2k|II*?_QMuR+m{!0QeT%6W z#vI2ckZ22CQ0{7a{`;3YG+{U!w5b3ZP$pXR-$3 z*EUMR4C}>+5tQN(A)Il;$PTdrN7wppqTp!T=xOf$*buEyEZMMaa&odG#8HIEQF*4T z5)-kujtXGf(cKIp9wF#(w9*q7u^?#+AA4&bzfvI*%>X#^5D7BVbNgz~)Dp`*% zX|Fli^Esz;I2(Wv~%}k5L$)0B>e+Yzhq@b3-{4B$p8aP@oD# zbVNy1L@mH%7;qu#Pq4V=GtmvO?C$|&3!-vzzk+8cx2{saEq4SgRcyzy%rDzMYpITs zC2ca|o-TLT>mioY<`8k#^6-jUZ$cr++!z8pv+peLZ62Rgig3wF6{2Q(hd%A_-V87Y z&lC0J$Ab>D+8%8#@y*S;lpz$@00Q;V?HNP#egi#@IRG&P6!60B7BTfQD z6?tq;AWUa{f)Vt-QQKe%VN}Zj_7netYatfsAgB}oR*wUEt5hoT9~S~kBecAR2ur2s zPzB;x&2u5{tpdw)T2)k*0ssQg@jg{fW~B7v;M7+vE#EqkA=JwRhiqA=hj~)WQ!q#8 z9+A8PK=b%?A*NDM^>ts5Z~`EJWB~U2Qgk+&)Ir${=Q_;+P)Ay~)rLS|gnlIw@o54Y zKwFgc!L(8$&$7@w6=QX1Vk_1_c}b}R^$`_n4AMscn$$2240mh1p-jrXR_GzJZGzwNuU*&Wv;C)Wz zT|Y$t*riu~#b!V50VLpU3g~4Ha?T9GAE(D(E$Lz6aSkB`Y_Cjg$<|eVg+3Vtaa76< zhe*AnLSHP7wxWhHZJ}X1G$Uq;zdA z00b}qo<1#VNw;)OS2CtnAd+ih&?O%gaag$Wk}UR5(+>pC?t zO{LXb{PR7XOLgU}S5U@y(RTNErEw_0c4;?QS#W2K3rtTpd$o6a{~~p{C^2uBAd0J3 zG|y}WBETkQST0un;(A2@r2rU;b{isDcBUnLh-e+gWd!(sc?ENf&0LJ{Ak@}6Y1ZX6) zdZRhIqdoegK{}*GdZbCZq)qyyQ97kndZk&qrCs`^VLGN|dZuZ*rfvGBaXP1UdZ&51 zr~ZBVr-3@Cg?gxox~Pr%sF6CUm3pa}x~ZM|si8WmrFyEVx~i@EsHSZ<$A8^x~}c|uJJmr^?I-Qy088EuK_!- z1$(dwyRZ%Wun{}46??H6yRjYnu^~IMC3~_dyRt3&vN1ceHG8u;yR$v}vq3ww--vIlRL?{KG*!#6^6>NxZ~O{KQc_#Z`R8S-izv z{Ka8B#$|lQX}rd5{Kj!S$8~(idA!Gc9KrJ&1WW)0RzSYDdLyAcSE{Lb+_&-HxI`Ml5l{LcYB&;@CT(V=`0lHl8hyvUFK93ztazE>a# z6#ddMJ<~OP(>cAJ=v9g*_plBo&DLN-OPyq1W@4CDPpTz;Jz(A z+PS^kz5Uz4J>128+{wM%&HdcbJ>AuP-Pv8)lVHeOAkrfu*SEdh?fu^IJ>T_x-}$}Y z{r%qoKHvp@;0fN>lYrW-9U{6K1wbGO3jX0CKH?>Q;wir3E&k#$KI1ih<2nAyg8<=6 z8zHv3)={A2P5$IjKIK(@0<=Y9U?fj;Pk ze&~rF=#K#81?L88-2{rh>7D-Rp+4%Re(I^d>aG6j=bXt`pym}~;X&XAus-a?e(cG< z?9Kk{(LU|fp4)E#>1`ebJ|O4Ue(veM?(P2W@jmbMe((AI2tJ_NLmrk|zyvzr3;6!< z5kK)2fAJZ=@g4v1$NdXBzy#u5kCYw+H~{iBfAcxN^F9CbK|l0GU(7iG1hzi#1xm>= zfAm?u^TtfBU(=`@R4B!9V=>zW(}+KT?rj`^7)~)qnljzy00+ z{VSgQuiyN$AO7wC{_#Km^?(2QpV#Fd^#S4r3=|(U{5yy+p~8g>8#;UlF`~qY6f0W1 zh%uwajT}3A{0K6n$dM#VnmmazrOK5oTe^G+Gp5X$G;7+ti8H6pojiN`{0TIu(4j<& z8a;|MsZy63KA?bc1BleARHX_eXmF|4tz5f${R%d$*s)~Gnmvm)t=hG0+q!)VH?G{d zbn9Yl`ZQ|Qy;lp`{R=p-;K76o8$OIUvEs#y8#{gsIkM!*H1R^+%Xgq(%A7lU{tP;_ z=+UH0n?8*?wd&QZJzLg`d1~LRt!vx9jXSsQ{@uKL`~D3)xbWeLU&p38o4E4j%$qxZ z4n4Z`>C~%Rzy8tqajTkXWB(35y!i3t%bP!sKE3+s+P5cP4?n*A`Sk1CzmGq^{`~Ea zzuymFfC3IkV1Wi6h+u*W*3@5Y|1Ah%gc43jVTBf6h+&45HHg`R8h!|3h$4c6 zcp`_HVTEFgF1`q3j55whV~wS$Se1u1?#N@0KK=+~kU|cbol}=>2ZWA9F3Dt*PCf}` zlv1jPBXZqMiDi~rZpmerUVdpBqmDibX{2-^s-&crUW#d^nr_OeNR%qtX{e%( zN@}U5;`wQuDm}q!tFFEZYpk-)N^7mQ-im9ky6(zrufF~YY_P%(OKh>m9*baNRfyXk%bZ@lu(OK-jQ ze!)_zo2}|@zyAIUaKHi&OmM*lAB=Fq3NOrX!wx?Tal{f&OmW2)Urg@38gE>0zI!5> zsH!PpOmfL4pNw+KDzD6P%PzkRbIdZ&OmodP-)u9-I&h4S3*!cl~zZasNzrNrNxWc;k*g4teB~PfmH|mJ40@+lM0w z^hlR~4tnUKk4}2&rk{>_>R?+9g9H&s0E5+=Yb`QLs^5-#?z->Jd+)yg4m|Hwci=$r z#dlEk?0weGb@0wV4}J8~PfvaI)?Y7h)ejh7`~%6aq_20jV^4nh=AVy#`s%OGKIJ<{ zz`gMi?5zCU%-7w1{`&9FfB*jf55NEpOlKH~ANMezv-))?aX}NH10M*%2ug5*6s+L+ zbhbSMb}xb8`xxgK=>9niig1J^ETIWch{6<3jbjkdAon1^vkv;~gZERM3U7$R9O`g~ zJnW$l!L_jpxG;Vd*q8!Ww6Brj_;0yETkb1 ziO57MGF$iBqXzw`lJK!)k(8{YB`=A|Oy04Pj*K59QzFTgXmXUKETt(=iOL7w3yqz0 z01U78N4YJ~jUh~>EpLg-T3UiqA`y@(2$uC|ibD7L+rZb-@wamGV zjbox^7omyG{%mShcmM|c2LXjXzzD7yXhC5Gf`m3e1F(uH z0~&AvSQ+5~5^a?P7K%~{)UyH&Xg~#C6;TJYfK?|&DFYnv(1RvarBM680}`rGoIatZ zHf87sT9wg{a+FmpEhC2$U37N)RGY8#+^x zI+XzrnAH&^Kv0H4fTBZnDo=@;QdW7j0Ty`GNgHrfu6hj!aouXHlz@SsGE}H)b!uCm z5Ywvu9u%t#eZU2j;Htg$)m48D>=WJ^*N@IsulQUZn)vE>ydXEh=$eHH7*GwW`=v=w0m! z+n@r0vmfL}u)qWOTi{q7`Bh$3=$W@mWi&S}rXQ_yt3G^F3IO2<8i0V5 zAiW099IY|xX=tZ^ppduX9)1w&yrQNxJX=L)B@6e0JWh&3EEpVv+4*Q zz4ZDb+R}n9_{OZ-KuaZ%PzwCG%}8x2L4zFVe0CYKn;tZ<>#S?0VSw4Q8g#3S-2h}W zK-sUF_OvOSRP?%9S=rV!Fxg61R*AI%9jMivZI$CzbJ)O$=68Sz+JFu8d;Z-_Yw}kK zYHE3(cg(c9w|J`x0v*@*(If@wzdtJQQ?n}H1V6n`w7xb@PA1Ue4D-g0ZM>-*|+`GTP@$qo6hmXUDaOh zQ-7z{kACx?pM4-6zGES$^~h~q^2V<}{8G zH(o>dQAPN3JP3V9Xf5{AN(KZ=vL;JNbboJ@f4RnOt-^8s{>NN@bpgaiVN~~fk#$lI zNNolsdO=uvx)+5OD1qd5a#2%Op#=d)7kzy=Q*zjJd}xAv3|V0LqCI9?z204Bh6OGthJhlmCz0U6Z*50!_k zxQ9*{Q$ja-9{>ZuR)WH}G)iy*ag}i6sC5BYjlgJbC#ZSi=qei!YAqOywIY14$SO&w zZXULYAC&>Q2!*y%jzKkXMred;)>IQ9_u1HAQX}pisV6iwF6L8+TLZ*DC2Z zbPw5f3Hgx{IW5CTFVqAl$5=~e7>KTt0r2RIRdtCiq<1Vk)K#AHFbT{ z*O1H>UpF;x)2EdvlVFbZk3go68;N(Drf0r3lchFvYv}-OiF6P4md~P+^TLuLM0Bf-}{34M0#l6_jqNPSOZ(&6bnVmSC)xhEjuz zPZ^79g_)U2gVRQu&G-b=7%PA|m|MA;CjNMtu_>6aLYnAhm9^4$nCOkJnOWRsZ~!No zyNNL#6`R2pcdr?oPk5IhmQ+f~Dj#NzsAyF>mYjUoo6vbIg$XzHH%p0GPASH8Y6b*w z)oHZ_d63q4H~C|p_ho9Ol%dI09uIaoPRbCRM0Y^Y-4{&L=IGI7^p`dnNG07@BI(Re6Dm6N8J*q6| z883&~If@CB0rp)QxpE=KDtp;@{_{qlta6;Mm3KAOTis}1_GEBHC29jlUJ$x}HbtgJ z<6ZNWrml&mud=2j2rF}HQ+1lB61aipNopS00x74D6(?}=C1Ls&qef|8bai2mwW&+F zmg9+~6SJe!Rhx7Le#T{x*(q5{pl5V?blLW)9Ts7>8LGvirSQU~T-2V6sbTZw0G zQ&@W!m3|TWc{!GRdDmnW>WNS(TlWM6l=V>`Xs5ilRgJn|NmGuY)c_D6qIO!WuL5RD zIc=;Gt!G880_gx}^#GAstBLBU76~hF#+MqPSV1KLhB%hCQUUksnJnn6D8;FAnV_t4 zd_wuI4l{$_8jm3fKlA1Ot*NSMd-?<$;I9rvXsda!fz_|*3a|-ltGAk~$5)#w7Z$JFS|t~`w>Blv`MS9 zON(YhTa5oyhA+vqQ%ki~YqiegOT(nK!c{bj!DW>$iU^LU#LwcuNv`OLBmVxQVN{i;F&aq_%zJ zwZ{au_9wQCYq^(;xtWVQXVgls6uF5vx$7CVUaGmJYr3b4x{O0bt%OCN+qR(#v!ax` zvrD_RYrAF>M2`McL?HLHCnmeM3%tQAyu&**ER;r`OSmIpxcEoB&Fj3+3%wMBK||y{ zG1R+Z`@7Mrz1z#Z-Rmv{G(-qwy}$XG zi@*7+E7}u3)q7g%?s|z^B5+UF^kQ z48~zRBL2%O#${~AXN<;atj6d;#%t`xZw$wAEXQ+98f{F+cZ|n*tjBxI$75l~eGJHf zEXadQ$c3!DSzJw9Y{-ku$c^mCj~vH;49SyB$(3x$mn77&Cwmb8yW4gSw)K3l7 zQ7zRru>wbZ0|A^PEnos2&;e8J)n5(PVcij2Z2~QT)lW_Z~=$@t<#z9 zA6KE;tepWsAlttU+`(PTK)~A_aNCQ`*h<|3D4^P%Z2=en0zXg%&LG^?ZQa*B$<9y( zKL7$4@Z6=X(`Jp@?xEXxodFhL0TrO$>&@Qn?cVPV-|;Qq^G)CNZQu8e-}$ZI`_13| z?ce_m-~le+15V%tZr}%w;0dna3(nvT?%)p&;Snz36Heh3Zs8Y>;Tf*s8_wY!?%^K} z;vp{LBTnKaZsI47;wi4;E6(CA-rwk*0UTi5c`@Fca}_4g+!=rY7r^5^?&CiWQ%>boZsk{w61?Bm2T;mj_H}M>6^~!o$l$M4(g#U>Z4BTrEcn{j_Rqd z>Z{J`t?uft4(qWl>$6VlwQlRTj_bLu>$}eDz3%J34(!1$?88p%#cu4!j_k>)d@B7a0{qFDo4)6gl@B>fq1#j>NkN)rpukZ`c@D1?cHwU0?`61py((- z^EGetH;?lZx8ozFZXj#_jPagcQ5oZ z-vTUO<1UXJS1k}QAozn%_=RuyhmZJ)ulS44_>J%Qj}Q5gFZq*C`IT?^myh|GulbwL z`JM0ipAY(>FZ!cT`lWCBr;qxnullRc`mOK!uMhjN{xAEpPy4lR`?rt#xv%@X&-=aa z`@awT!7u#7PyEF%`GDW|e%~R;&-~5r{Lc^l(J%eePyN+z{nwBE*{}WE&;8x+{ofD% z;V=H979l&;IT2{_hX}@h|`LPyh9A|M!po`LF-`&;R}J|NjpVHv|qO zSkT}>gb5WcWZ2N*Lx>S2PNZ1T;zf)ZHE!hC(c?#uAw`ZPS<>W5lo^4nWZBZ?OPDcb z&ZJq>=1rVAb?)TZ)8|j1L4^(_TGZ%Kq)C-7W!lu~Q>am;PNiDa>Q$^+wQl9w)$3QV zVa1LmTh{DZv}x6@W!u*6TexxM&ZS$|?p?h8dG+q)+t=@3z<~u1CS2I?VZ@0QFJ|1> z@ngu5B~PYY+45z~nKf_b+}ZPI(4j?-CSBU}Y1FAzuV&ra^=sI%WzVKv+xBhTxpnX6 z-P`wX;K79tCtlq6apcLBFK6D|`E%&erBA0`-THOx*|l%y-rf6m@ZrUeCtu$DdGzVk zuV>%h{d@TF<e*5+JUw{J^cwmAHHuzwK6IOU(h8uSH zVTdD^cw&kxw)kR6%QpLLw9{65ZMNHX`)#=6mV0ix>$dxDyz|z3Z@&BX`)|Ml7ku!ieQp7UfsW#N z1r$sG;qeb5mwa-{E4Tb|%rn<~bIv>W{BzJl7kzZnOE>*=)Kgb|b=F&V{dL%5mwk5H zYq$M&+;i7`ciwyV{deGl7k+r+i#Psw$9Y9Oh7mJLF*xefUEl22qGZBw`Vbctj*7QHe`rViTSCL?}j4 zis)0q3bGf&pUe;hL6~9}z4%2ihEa@TBx4!Pct$j)QH^V4V;kN0MmWatfI$EO3b4o% zfwZ7;Hk4x@{rE>f22zlNBxE5Cc}PSiQjv>fWFyz8gzUATdpptM5gd6*OlDG(o8)9C zJ^4vchEkONqa;UsUuN>^HPn%m@NH@*2yaE4Qy<0NM}!HLRX zuJQ&}G=VwY`A&GoQ=aprXFctCPkd6)1SVL4HEDuP5ZoZ210`rd4SG<7CRCvdWoRF7 zkV;|N1e+hwWSOT#L6md0~s9W#) zSHK2Vu!ALRVLh5xwQ`lMel=`k9s5|wMpm+uwIyQr`p?BuRwqa`U)~O>T6pdtK~iSG(JFDc zEqq}NXV|3@UI~RY{9zD>Si~cKCx?OHVG^fU#VclUi)m?M6uVf)Gp2EkZR|%FgZIWe z=5ddG{9`8OxVk_la*>UEWF&h~$i7W-lb!rzC`Va;OIEIwt$bxHXBo&;)^eA<{ADnQ zn9E@%bD7P2=KGEr&1+_Jo88=8HNRQTbEb2hK^tc~=ULBt<}+RK+-E=sTF`?Y>YoXH zXhbJk(HAxJq8WH-B~jfKGIwAC#K)V8n0F?TN6#%^e=s)u@0nqEc)Qdm~oIn&>Jp7pew5tFI)V}qpfb`3{-unS? z%eu>hImcrFY7>AAn7QTSKF|v{0jL1?OTUGqzG^dq>4W|_99*Ao3pWUupU=y>09d;B zOSq-OI-F~O&HIPJ8-Vshx^T0;e}Ka3GoSF|xCL0k!2^IFJRb?5wsE6CF&slO&z*cvhhB7)d+IvbQi^Kpj%d$%8GKDKK+8zg}5yMPzGL%MUke}I4}G@s6+ zLokd0XD~(%WWvaU0YgkUDP%)kRG*bAfFCfQ9R9e)n0vWq3xK?H24Ji_^C3Ou`ocYI z0VW8)1@y%1d$umLK>#oS{o6gq>%*HXKpX@CcXPsQ6hG0ULI6lUSZqj#d`SK|!}@_o zikrb|v`EKW!hg6zbnC&cvw-sn#J4lSef$R|V8ikA!xluvDl{LKB!G6J$a*|DaZ|e= zxJ83xABrqVaU{o%d$|s1fEGxfUNnGzv&ODtERU=<>08R=TgvT2O58I@W^}rZlt%RF zKH6Kxa9g^w>pBL6NUj1OV%sJp#Zz{-bQT1vpCe@yCDcNu0YwCg^|&5WtZ{O2U&% zYGcTDOvR1VK`L~&iW|K6Lmw2V00?LRw(G(Uph5Gg%6;s^vb;^)bV!Mup9H+f(WEwA zw6Zy6{^7{6kEx8@I*_02yo_703O}fnncV6&C&P~$waNTYopBofPgRffgvT&W!1DI=Hw2I##D&A)#D)uvmxo4hy>a0ZR_LG)S5q(xY&ty*AH*zHWef=!<~?7~GQ z!G(iS8|7G)%+&MYMUa)&64f{50@Y{BNqyu;`N7geWj9B)L!FCK+9Ov@^wr8M+LrXc zL{(6g?YdR9TK>dM+*rd}^;y(WO&=Rf)=bP+-a{X~6~Ex4)V;J+^yz@P7Phk! z?y1bA{;7G>$QG_)8#c2S=Bck0H~YI`AO7Jk%VC-N+93pDBR*m&6XKYn*##&(Bz|Hj z<}oFXVk)j;E1s{jM6R^FVlM7tFCL*O{$ep6V=~^(ET*n4E@L%bV>T9`Fm7Wwj^iJG zV>zy4J04*=zGFS!W5vy5KK^4s7FItFWI`_F{TyUNPGm*a%`^TkT3lpEj^qtSWJ#`M zOGZ3OzGO|_`f@#94rXBXq{8255kq_NtX`>GL@P2v~rhUVsVsH=G6n2SDissJJ0; z0G1x$pgx~Lhybmw=?6$_r;cm6ZYHR{0G4imv|j3s{As?H!jm>rzBT{}m{tD12F)Ch zYQ8Q22Y~CjZfwWaC8%CTl=kbpKA#6@fDDK?3t(x$tLi+>022snls15KjexTzKbu2o z2pEJx5WoU_Y}k(N7c=MtL+An;>L8d^l&$JfWdg_)K#Z;G7d4-gPU%an>ABnNl>WCP zm{kU-DA|r~>Gq_^{)Y-+X$RoD%08bVkN^i@0Gh5p-tI$QtZBxA0FPVD`DZ~0De1s|m9{)ZgU>IG2l zs2simU;qmUYT)+o?e@O_pFRS2ZT=2%{+7`NFL4vcG1{Im+a{p@p8f!ob^xu8O5kR! z4Cih1k!k}#-~oSa;O@{9|8XFXqXxHM*GB26#BoVn0j(DA|Hg40CvX>E?jWCXDo>*! zH_W!S>H>djAV6#;-*5oWaRDdr=0@oUxVAyK00;2#DsOW)ccLp-)1Qt?yAJHI{&Fqn za54Ar*Zv0%py|Nw;Wr<2LNB5?uL98?)eC5A2Ec$CZ}JY`L;^P-ununqhycbebWPv% z9SUhq|8!7)B2N!>QZIEwPw@_0aZ_J)R@bCaZ*^FYbrX7ZS+8|lH$zlEF;%~HUhj1_ zqIF*nc41E-U>|m4KX!x5^%z5TW^eW+Dt2d&c4=oHXrFd#{-5@0zjkeB_H5sFZx{AF zb_Q`DcXBUxb3b=WcY3dPd%t&l&v$*_cYg18fB$!Y4|ste zc!Do@gFkqLPk4o2c!qCyhktm8k9dinc#5xhi@$h`&v=dBc#iLQkNLQubXBAkBw;+|Pa8-+kWiec%6m;17P`AAaI5e&auWX_;FN5VD zAn~tz`Okm--+%t^fB*k~fFL1oAi;tL4L8d) z?<&>F)2GnBM2#ZFE12n*xrPrTPONw_e=Ue>gwdR^i< zlaCgSStdb8j+kVfha#G&qKh)xsH2ZUn%SLw%A}`qd{X+)pA`jarid7k7^$eEl3J>% zr=psws)$aSsimi3O6i{*JRkr700iK`1%?VVUjrmi5Wua&o=L)y5by_p01t#PP^bh& zfGY(8k+=Z^!gg5Xs@rngt+(HT8~(1i<2KeRrLUp}Sf(=hxIqE827s*ryprH62^J85 zZM*?2AW(?&#XIb;)Y2tj2XZL1g8nDv@{D~fCU0^Fkj6X10A%`Lla%J(MN-~F`gYS z$KJXzX(&OwCAa*u$^x-$@~wgjWPz>PK0DCC33KQnK_QIUFVbtX-L~6r!yUKWUn%V+ z(-k`Xw1)G2I*`{^0{}4zS^L`|1_g~7WY`AZtT2CE3)$g^b5mZq<(Ffgx#mUJo#ox@ zsjD}bgVSeF*jJMdxv|9({xs&mi<2-m%}Nd=HNu7-Ry%O4<9|r@H2??0pL>2n6Q$ID>?! zS^wK02RrD&4}uVX0gTW9Lub6v-A4d3BZ$u!D7eIV${?i z2yj0lqA`tXTq7I)+ejrNng@v&lGW&9r!Q5@DiRV` z@pB_18|lbLLNb!nfunQe_%uz`5m;loRRJV$x;+{TTQ!8B`vhk#Y;{t0@RLBY0>FUD zNivqQoFy%5X-mXV(m0kBU6>GJJ*wR@n8F+;F^g%;$mDW0yHuh=DrmoBLNl7uoF+A^ z36*3Xr!?u=69MU1g&-gVZ2(Z4HOpzvbD}ey<_rRPF1gHgwZ;e=Sil3uC=d#~OJ?fa zCqMh?&wr}X2*kPMKi*luFC?J^t#lX{0qW3)LNuZh#IHgiNg6Y342MnEY|YiiS*;eb%dAP7%|5J3z2Q!aH=bXGlr z5r`0kAp9V%a-Az(>uT4#;x(^&-78=F>es&lHn4&nEMW_4*ux?=v5H+RV;k$($3ix; zlASDND{I-yVm7my-7IH2>)FqOHngH0Eon<@+S8&owW?h$Yg_Bu*TOcovYqW--%11{ z93cr%#pqeN2~&bL;B#Ef;YV49WQyyYu@vsH@)gzFMHeT-uJ>czVe+fed}xA`{Fmh`rR*o`|IET0yw|| z9x#CmY~TY!_XxSI!WYO|R#xkBUjcaV<8{;^~I^Hpld+g&M13Ab-9x{=OY~&*&Imt?1GLxI^ zG=OKs{?qdL{9zB8D^EanKK^|!9pRE2A8>s#YG*Sg*{uY2w5 zUjsYX!X7rUi*4* zUpT`X?(m00JmM0cIK?Y&@rz?T;~L*M$2;!vkApnq=NdDt$}IAeqdes*UpdQL?(&zz zJmxZ=In8Ts^PA&5=Q`gx&wK83oA3MQLLWNOi*EFzBR%O#Upmv9?)0brLp|zJpE}i> z9`veXJ?mQEI@i1I^{<0H>|!4~*~@PBv!h*0Bu`fo)9&`S!#(bDpF7>_Zuh(6J@0zo zJKrU=c8{67?}8sZ;R|p0!y`WNieEhA8}E3XU;XitpFHI&Z~4n(KJ%L2Jm)(v^T>NX z^r9a<=}T|=)1yB1s$ad-LC^Zv!#?)1pFQnsZ~NQhUi7ZdJ@0$(``-gU_`)AP@rzIK z-5Wpo%3nV7oA3PRLqGbIN51r{Z~g0IKl|F>KKHxdaq4?N{Nf)!`O9zq^P|7{;7>pM z+wcDO!$1D=pMTodPyhSlKmYpQKmYsh|A*V({|Vp#5?}!u-~s+3p!@}30y1C&I^Y9B zU<68F=`G*{THpm@UE%H0tdg3R7Vkk15B#L4wn&K&psyIhx}+M&CH1<2tfqJG$fSaU%s7Kmj~JO+-Ke6hH*T1PII{KKdgATmVfp zKmiy42)yG#B4k1;q}#>gJk}#k++#lOV?X+%KR!Tc3}itr%0xsCB|*w$Qd;FzVr5qH{@YSQBuP#rO75gk$^=nrWm%f#S)!#b zX(H&@;?u~ZNOomY`lL)u!9W(k1dJtBrsZDpWnZQxSB9lrKBY=xfCJ#815`m?YNTH> zW@9>LO#)_AdZklVq(x4_OpxVcYUXBg<~l}ZOz`AaR^|f~2WIMJXQF0ms-`u5=0Aic zW&UIi7$!ZIM`o_(ZQ^Eb>LNFGz&tX*5zs_X8bAenB~(tqY?dTUo~CXxXLCAdDRScx zFu-KS#9J;v4iu+M^d>=afMQnVbAo4hiYFtEV??541Vq4W5{XRArDUSyW&T4(R)9*< zCvcAEed1?+!r?ehK~PraVZtYD&ct#4;v)no=YKY(ek$mKGUyhnqccnZ1N5T-G{6Lu zM|;Wy%n)Qi+U0*HXM=)hh>9o?s^f^7=!v4J8J1{@vS^FCXcVsKi^}MX(x?u`XpQ1% zj_T+L-sq0{=#K)a1NLZ;5^0edY5ooAkt*quGAa2bX_G=}luGIIJ?WHM>6KzB?^S7* za%q=(>FI6hmx}3_lIirV7N2>pbF}s5^A9u>Y*ZPqAKd5GHRnb>Z3wxq)Mu}{o9IW0u+2zrfTY@a%!h41{7q% zR@Q0Y$qJ@=>Zzh?s;cU$Ud8@P%-T3Q1FOpFtkP<&+UiC0)YEWdtlny``s%L&>#F9e zPx)fW0b}`?YOpHnvNCJ4+LWu7l`}d+88~aTTI;o9t2&jzurAH09&463L$YS;xRPtR znrkFMYg8pitfguBIK{cbYrM+qyn>K}t;;bwLt)VCzVd6o`YZ2Tl)7%yFSe_fw8p<8 zY{Dw+!Wzv$3G7snDuzG@!%}R;TI|Jc%WjQav9jG92~~X1Pgqz_!lcB=LJ!8GY|5(a z%5nuZi3TfbNXSaXeUMYj^oz-=1kTFrQ=HPREQ=KhNh^iaK_Cmf46Q*FEv_7GLQqi; zvFy`AZPcQa%MJ$2j{eTh4#Wg3K+OuokZ4OsaKO7PQrM;m&X#PvfD!eGL?jA&5Eu-knT9Oi~x*o=_Ult8gA^$?(9kv z;?khE7A%j%tma}6>qL<1XvD|>F9`_mJzZ|j9xv8%1lHOP$*#`c(#+S&kKl5M^d8I# zRd4jHP(hIGBGK;mf^YaXlI`~5y4Iq{y6fVq?Ljy$@??Yp3Bb-21pLac*`h?|t}jSn zZM2w;*ho&-{sKVd4g}c>N(Bip&%8?k2k_*Q00AFF_;Lia=rkQ*>XVMCejEO#OL`e;qOxG@P>vJMpvGE-0$AuTx@@Acf!*KWX= z2mr}afUOh&$wGjjNWcPYF9x{oIj8M3PYwxRb2>W1u)5G z?>KwNLlZ;=MKpg%F8~*_K*+E_5Onnj$_4B&*Id8@OpXGPi3VBnMK|+GvvfdvHY)2CW*}^at>2yQfNB`n)M(6&t z=f+F?D0SgvG(jkD>LwBcXb}DI?*@RiU1l09KS+MKNaR`mLYbyp7IbIXo6 z1#koaE!I*|Ym)#I`G;zwx8{cRKom85Z&E%2t4+~r8c7E&ke*UKjOy5K_ z!%lFw?@v1}7Yh#HQZXoZO@cG`@jAE3uD35G3%uwtb|)|6s?viWFWc%%H~%(5;B#!- ziv!0HiL*}w7j_KQj$;2Uhr^49qp+>uiUZhFChyCQE4KXd_m1;;j}M4{-$-vWQ-C*) za0j-E^R&~N3D<_RK{R)4C$=v~IQj&z1k`xrWOwu=jB`tkFSGYO-Uao?>dl~WOazE zGy<=0_d5CUQW29s?-(f%BbReVSN4Ugttv&jWAE}dQ_ar)4e(OY{wSHVK(u!Toz0kN zi>kXf0DNuWDtPZ&I-)aT2C&ZrfMa-(SCHTi#CTiLFe}MIYctJikXzdjl;dpOQZgAm zw%7>E0!&T_%rgpuI|+CZmTP*kOOM1W$vRiOH@|Y^2(CC?b3y~~IF&SiG&L0wzzU@< z{;QVT+)EJ+`FrFJE%4OMT~i z{^yqk)su!yt030bG_h+pTeA`y*Y*R5(jWB_VoD^Xi%X;i54|_6lqeWOPMxx`V?wPCWD;)vw9V4R;^pPYWRQx#tk52 z$(9{^0|tr@8o0J~+t%42CNCcVFn|$)-4G7*8rT~Z@Jj~(7z8$a7;$37iy1d|{1|d% z$&)EpwtN|LX3d*9cdk6O>RZvGy@C~M7ByP6Y)7}|8n;Rimv!%23}FBO1c$eCduGfq zp>N^Ci5EA19C>o(%b7QK{v3LA>C>r~4E|Dus%!08ONT|BI_=u+g(CJ zcmE!KeEIX~*SCKke}4V@i5tOAe!T8Z`;IKt!~;+}XTC@Rhn8sL55fo~oRGo_ExZuJ z3^m-4!wx-E42LAX2o1s31SGI516eC^G}w-CP`wXjoRP*FZM+f39Ch50#~yv;@CfWk zWbv&NQ9LWW6^VRH!6eZ85y~i~oRZ2at-KP;EVbN{M=%gJW|ClMT5=B8@ODP z%{JY96V5p0oRiKv*Sx_?A(MBD86w*i~Rq2Kx z^2|<9G5Z`4#XvKC&AcRzFoK96{(|@c)l^komDN^VeHGSNWu2AQT5Y`**IaeomDgT< z{T0|?g&mgIVvRi(*<_Vnmf2>VeHPkirJa`AYOTE%+ibPnmfLQ<{TAGC#T}R2a?L## z-E`HJ)l?#kI3fu>KkaJMKLJg0UbqJNq6$$*H3Hy(1s<5-f(kvtl1)At<&;%kndO#Uei`PNWuBSlnr*%r=bUxk zndhE;{u$_?g#Vijn(MB;{u=DC#U7jN zvduml?X=Zio9(vUejD!oxaFRk?z-*18}GdJ-ka~f{r(&9zy%+i@WKs09Pz{zU!3vA z9e*71$R(eg^2#m0JnSltrV7lbtzw!$Pd(2n>Oo9D9re^zU!C>VU4I?+*kzxc_S$W~ z9rxUI-<|i~eg7T!;DsNa_~MN}9{J>zU!M8qoqrzs=%t^Y`s%H}9{cRI-=6#Kz5gEk z@WmgW{PN8|AAQ@S9(^m&zZ0E((xaZA{`&2|AOHOI-=F{f{r?|;0Tkc>30Ob_9uR>E zRNw*`*gyw95P}hu-~=gHK?`0GgBjG|207S44}K7YAr#>VNmxP?o)Cp8RN)F)*g_Y+ z5QZ_7;S6aA!1(^r%QW_a-wk=#Lm&PSh(Q$M5Q$hsBOVcnNmSwznb<@pJ`svhl;RYr zSVb#d5sO*W;ug8sMK694jA0a`7TpJ))r=91X;kAH+1N%mz7dXbl;a%fSVueF5s!J) z;~x3gM?d}%kVY({S#}smLmm>5iB#kw8QDljJ`$3Vl;k8SSxHM?5|f!UQN8|DNKJkc zl%W*mC`nmLQ=Sr)sZ`}ES=mZgzS1p&Op()ISxZ~q5|_Euce^oZ%GbILTQ~bLtVBNIK&= z+1XBazWx)Q@s#I0=~+*Ek}{q5TIW6a*-wA|6QBVV=s*ctPZ)#ye!+EI^w6r|;}=srVQQj?w(r72bEN?F>{H;(i`E|uv_XQISVRABPds7Y1oQkmLRr#=;`V?=6HsajR5UKOiZ)oNC! zx>c`!6|7+u>sZNJ(5{vht!Y*3TG`rGx5DzQZ|hC7Oh56Jq=i-NVj0_5$37OSafR$;DO*{~UKX-sdh*VXQJx!YauHZ;5674LY-TVC^aGrZ?j?|RwWUiY#xz3-Lp zeCb74U!wTwnudD!>O;@PZlK-~uPu!4a15geg2;2v-=x z8P@QIdwXFHe;C9e7I9}iJYo}{7{w{RYl&6dVi&&{##d!=jA>kB8{gQbGsf|bdE8?k zPgKWRoPq-i;D9NxXvjrA@{gI^WG6p4jzJy`2ndtZH|^8{QRWHCJ4n6(Scv&;RiB zn+x!Os^s9=-463Va=_>}Pa4@pzH$V>Ep2Kq;NDj@Kn4E6ZHa&T+2Q^sxWV6C=YM#a z0Rg|VzvEqUlb?LR^WI9mv6Aoy{No7EHh=`2afEwM;Mi7X_diwufff8C$y&X%G|M@_@>_)*mAdWzDdn^rbA%(S>gM{KYsZt2*1>)m;V0rsXyG*V`cUKKz;$j z&T)Q6JLq4AK-K@j=mnr)D+y=(@59Y>=$pFuy{EkEADdf z+%8Wms=)F5&fp9{0T<8!8o&ttVGtyM*c`3=fG_>{PyX7^_ukLyqHprTZTWiY{``*R z)DHkp5Cv24vIG$C^e-y_58A4Z$jGcn4DRJ-u%}qh13yslZVd>qV*7M2{&-OPtZejf zul`C9167a-o6rgWiUsqp0kVSB@Gs|f&@DQE09~$gOfUm`&;X8b1vU@;B9HsN&p1mV&QJa-i27AOpUj z=#Fj)*{=t4ZxA}b2CstD7{COGunO+Y0Y2ahz77MG5Dw{(6id+*BkQ+9r?2v*&G>ER zsL<8~&h_4G0XV=aE+7^c?h4nB=^l>QuN`Ykj>as5t_8Jp1=f65MX z(bp#6_Bu`7u&e>7@gF9D*bLwSe()-Qpw_U=0npD2i4fdA;NHBE{H_86dd&bV!2Mv3 z*RT=tT5t+E@EP}!ANz5qQt^FO(T?WP9}ChT53;JFP>Ki=Asf;m9}=h(GKv@yA}i7& zFEXVh5{f7iBRkR~KT@JJ@`*SSBummHPtu?ML=q)ek|kS`s8rG=V^St(vZY{>CT|ia zb26iBQYU-TCx23)coHawk|>L^orKaTlTs;{lA4f`DW4K5qw<%WQYx#`Dz8$NY_f*7 zk}JE?E58yf!%{5Ak}S*8EYA`x(^4(hk}ccPE#DF@<5Di?k}m6#goH>W=LakIk}vym zjP%lnByxTHk}wO?FtKPbZ%82%^DrCJF&`6&7;|)@P%Gd~kF zLlZ$f^HT&cE<_VGQ&TltBWa$-G^=qnV^cO$vo$#rAk8SIlqN1jZ8m$;Hyd*|C-XK5 zNg($`L3r~wlT$gb@;HUlm=G&B^Tqx=mXkWG6D$A2b5ajBtdl#tb1CJ;HlGu?qVrSY zf(g76J=4=CK_fedbC!ZDQ_NF6=aW8Tl09A1J+lcu1w}sV(?9?7Bkwaj4{JPgFh1o1 zKoe9!A5uW`MLQ96K_gT`_pw3e99geGe6adJP#B@Hi(2G?o?0z6j05JPkl=~RdGL!(?tVSQ5SW*26a39lu;v9 zQk%%-M6;xx3Qw@t#43$L@bwoo|RaaG1YtusuH6RaFOB0n< zYt>duYg9+WQ*V`5d$qB0l{B!lIKyO1ebrcx)vfo9 zF7|U0_GuQDV@H-`{yWxHQ#4%P^Io$xUrW|yUsjRJ zK~^`lwP%NxXvyhkQ&vA$mRyClXq(n)t?6jlHCC0DNJRE&uNG^YDQZh~Y6F&KvleW_ zcA2(ThlX`&TUKn-R&8&IY>#PNtCnfkR&M7umD(0Mz4mPBR&VzfmF%`a6*g}9R&WQm zkrrS9uEKBI6>!m(a2wZg0m*O;*D4aXYZv!%FBfzDC~_tDA1Jq3EjM#RS9Iqna+M8n zPnJf3HealEbX(VTy{L58&vV6VJ&_hff!B7icYDj%d`qNzIk$6@ zw|c>MMa8#7n)iI?mwrVfeIs{r**8h8S463|e*YJMt>S+1*BN{_Y2kM?JJ^WLc2@g^RKBE((|B~7#9iWrgWK1JX%~UvwqHi2 zR8Ib7UGi9u_n43S*pL4hkONte2bquy*^mzzkrP>w7nzY8*^wU^k|SA?C%IbMA8mqc=m2X?OO8n5H^uJf9&yLGSo8nC1FuLGN~dv&l28?jmS zuoIiHKXtJi8?q(!u_K$Z19h@18?*8BvNM~r*L1Tx8??RjvqPJ-rF67Q8?}q{v{Re4 zadfp?8@6BcwPTyMHFUOX8@CtqwsV`e{d2c_8@SW+w}YFwt#i1G8@ZG7{2K;t6M3R#4{$vfw2QEAfq82h`u4_weR(g1JG2+70C0Q|m{hSBz0 zP#(O8q7Kof8_p07(FPB}KOD_85*JJD(*WExEF1}=J6|ST+CBxve<=GvT*l8#BbQI= zzHHz+5W;h>6?GB1xzWlNK-)}s9B(bx@G;%iZOObZ@OBaVuJOwr;Q4lq*cM;{s-nmF zFT(!;0~)~Ayb&DPZv~3n0hF%L2+%5EQ17r}20y^iXwB9H(Hd?3E!$??&NECRhp_>i zq3-YvD@bj}y)Db5Z}}n)%d%VFeo^PJyvjoE%}9>P_3s?7&CNjY7$!!G(JB4n?p)R-Od??r)1A)}t1QrU@5N`a-*P|$ zz%KS=j^=Ex!N;!b&JOk}aKF8<3LyXmjvx@F?E$#1DlG5Tg0a`{yU%CN2-FeVs*c*e zOahLe3RQgNN_`fOu+MR>&AdGW0xs14VZ;}J%U~_rXIrCpMDHqCoC6m;47Wmy30~G(aM%7}PUb%S-*4{sB;G2vui@!U z-Laj#`Hu7Q@8Ydu3~vnu0NwyT&no&n-}YVSQ|`Sp#n4)q_sp>44W8ir+$vz;(-`0Z zmv7l+Pw9VNADO+yom>myj5mis0vvz=zMR)}UE}>u1M=<#Tkqk5Pw3Cw1+8MiTOR5A z4If+BDweMT+J4WkV#G~+=ks2_6f)r&{sS7F{02V7IsWU>Fwz?>>eT%lDP9LBjnq#5 zDx6{auq@WP-t7nf@jK1y=TY!UJ?&W!{{Sx@@lpABTpHaB@AY2w=PL>qT)j!Y^Xbp> zhaM6u@XHub0Ub~oCw~4K-wYwY0~x>qj_kg-eg3^OA1kW<j{BTAe|v7*I`7&B_z$g!ixk03*e z97(dI$&)Bks$9vkrOTHvW6GRKv!>0PICJXU$+M@=pFo2O9ZIyQ(W6L{DqYG{qJ|GB zVB7#gwW?LBH(;Rnph4z?0tFy|7(152LIWWFX;|2x%D@#14pJDXkfFn`2IdY7!k_?y zApatOjUcgZ1BZeO5FXf|zycHm6BEu6{?|YTh>a1xt*p0hLxOnc@;%6bpfiI8Ec_e& zm!R1K4=NglIN>0KgC7XiHBfqBhJ_4{RLR`Csqy2;lPh1&yt(t|(4$MAPQAMI>)5kv z-_E_e_wPTSMxA=KeAca9VNyPr-n79268;_O8}^6>`g!S|#Q<6^LDqnoP#FYA{4>P_ z4x}|(WP=gZm|}wrB$b%l1w(~;XP#DE#Z+4X4dm$l(2!`ll^9qDp{Cbxa0Y4OUVXLp&ueuaq##>}HFzRe zhfQc8hjspS!9s8vAm>1I!b#Yj&LEm-4tV+n*g&r(Ss0)YAv%FWbuGXsoDNJeP^Enm z>gB7j#wzQqwAO0tt+?i@>#n@^>T9fEk|$d|uNt@qx1 z_wD!JfCnzPbZ-wE_uvuj_CSFUwT$@VkVh{0?7l^R|hVLfMh@b=~NWltP@PZi3pawU{!47)x zgCGo{2uDc55}NRYC`_RWSIEK^y6}ZCjG+u?NW&W1@P;_dp$>P*!yfwZhd>OX5Qj*_ zA{z0CNKB#I`N56jG`2$_`#ew&{#B)AOp9^#V&gBi(vGh7He|FpdIULVN9bM z*T}{;x{-Ggq)8cP62~{%@s4=RqaOFDw>rjTjxq5g9|uXuLK^ari0qgkT>{86Ci0Px zjHDzdNl7Oyk|m97i8K0D$xeFmlb{TxD0wmnG_E9*EZO8JSINp&y7HB%?h8DAh~Fqg^9W;*kk+(V`^t5!>4!7Z078H8J) z$<1zh^PAwLT@^Tq$!gvbmk(UYCYcb zKmY<@MtK_apa@N8U+!c{OaN1f>t@rvn}!RjR55rcX_)TGz_fcVa*QWvxj5 z1pHY5p4Rh}B<*TCj~P^c{*wv)T!2u|s#J%xzyWZjYXrWQR1N&bp8;UN4+6@7l>&eO z5con{H_O@1de)0?jjKfF3V^!W)uAN$=}G%K(SPn~r4VpPW7lNafm&9u1W76cRyt6+ z+U*|_;B9XKz<|Mi_PEGRu5!~C+Nnl_tVFGdR`Ghzl6p0wtR3oS93a)pAQi2zC29*+ z6@irkRG<}g06Qz?&&B-10!G#BKPCWI_b&Io_|30=mDgOeMil^IqsVksqS}&x)gfXX z2oVZE+#Jm|T?-(papxL<3w*c1rit(ZRu$o+lGOm)+^>j7OyUxouD=9{FaZ9r8j%4P zGq2a&>sPZIR@V+>0xMn2gEb(40F3G(Ibd*X&k6wh-mA1mP>TQr5VQ&eU;t?`v6QDw z1X^I(ENptP7mvp?rd=fYa~&UntV zo~71Y1`EK*0=_GOVQjeT0@b)-w(&rM3}9!R8O{;mE}OwSkT(|~&XBdOrU@XiJ$w4o zpbj<6LMv532O6~_@$yK(JPN4Uzz(*sFZNae zaD>a$tgc?Ms7@LqSkbE@vy%&Zfr0+mA&k~BrT<}ROjSDo6&SX-$No+3a&yV7TNd<- zl^tVd^A*vphW1++T-3r=A+Qq$zyLbkW?f(UA4Xtsn#@h`f*btcRzkK(R4o!#|HIX5 znzNm8Ox($)tGoahGGT+(Z3+3)0Yo531{iQ{gq!^2C{MX^6#h^`Z_d!Gh4;hdy{iA_ z`pucPtk-h8?T)95yE(6D$yHADq8t6_IAM8eU9LKJ&zK3sgt$XiK!Az{?LM_#K)7Yw zb4a@;%N5l+(#KBrvYUO?N>4S@aR+k)2bG*>kGtIGPIsid9n~UXct|!`g%H>M?|=`y z;9=5tb$5B~#*=YMw8^-^JO1&Ik32?%1NT9*yU>b{=ogaU{&&fH{_~)xdliz9%$MkW z?~Ipk=vUAB)}#Cjl*fDC5kI}mYd#4^P`&GS&->no`U^%Nz3gXy`Ip-ssG~Om)P2wV z<~x7H&R|0D$2t5j(H?wGZ+7yDApGZh|NG#7Dl(Hmgy|dJ@?34VM)^AWBN%}QK?uSR z=1>3n+yDOf&%gfn&;S1W|Nj6OfC4yx1XzFucz_6)fC{*P4A_7U_<#@?ff6`@6j*^4 zc!3z0ff~4h9N2*#_<9B=hjdtnc6f()n1_0}hkV$F ze)xxg7>I&6h=f>(hIoijcmz6F1z*5}J%~?jcTAfkgjK)c4eilkVIrg(~|n2M^nimcd*uK0?u7>lwvi?mpaws?!Un2Wl&i@ey2zW9s47>vR= zjKo-s#(0d#n2gG}jLg`K&iIVb7>&|6jnr6;)_9HBD2jd8h?g`{#n*J}r%9VM2;^9f z=6H_in2zeWj_lZu?)Z-I7?1KekMvlN_IUn}_?VCSxR3nUkN)_N02z=1IgkWdkOp~> z2$_%yxsVLmkPi8f5E+pYIgu1ukrsK87@3h8xse>%kskSxAQ_S(Ig%tP5lQwyiIGK|=xsyEElRo*AKpB)mIg~_Mlty`!NSTyEWJN1! zQsMWMP#KkRcY0D;l~#F`mSlOBXqlExWtM8$mTvi$ z3B{IhIhS-6Fnx>hWs=1njgPN@Qny?w0Xycl) zS(~o!}XsI^mt-S)S&3p6ix=KNyba*`DtCo;E?A@HwCKIiK=bpZJ-d z;d!6>*`NM7o&5Qq0y?0>8K4Aupa@!<2AZG@+MuPmpbi?L5_*{sI-wSNp@&(a7`mYx znwJ{dp&%NfY5AcdTB0Vpl_YwiD!QWJm!d5CqAHoq)3{i{=#;oO4_7Os%K34q*6MiB^ISrdZk#ZTvnQ;T-v2s`lyh)MUEP&lv=3(G^v)FshT=Jn7XN+`l;p9sh~QlqySzs@kfq8k%%^e6KpIw5mX?TC2F4tDtkMy4tJ0DmlFRtHL_0eiN+3daTH* zHpZH)%-XC}v#ib)uHrhbf z0FJn{R!gZ(`vp%cwOre^5Q_k%ceP@>snV%UUVFA^JFw$4wrp#u%(+f!8@F=nu4&h{ zcDtu<8&q?e~81bth$eEYY7o49B?xQ5%faf`T$8@XZ1xQ<)7T?@IA zo4Hs@xt9Lhxkrn+nj5-L%DJ9fx;hKGqMN!%O1h@ox-5&jsvEmM%DS#wyB`a?vYWd$ zO1rk(yAq4Lx*NPN%DcW>yaNlo!kfG%O1#F~yz+{?${W2M%Dg!107LK(A&{;R;1(0$ zysule(i^@jO1%N=05>2c?P|T)tG(Q7u@WExWiSv^AOaGwwce|)&?~!9Qdu!}Vi zAz;7es=nEauG>3b>52dm00pDvt`HCcBH#eVo4@;8!3WyE@`?Z2;!4`bO{)xfy`T$VyEHbbs>>9!b+`i`; z0sfir4^R*RPpiNmysk1#!@g_7I6TGmsl)IZA2;9ts*%OziU1bCz-3?o4{*H&9Ig)# z0`Ut46L7%YJHS6&1x-xC6CeXeoCzWD0O2}x*!!?090U<014^6#|0@CF%ERwf$UO|h z2O9zmjKmWFt`HEv7C^45u^|O3#Z;Wh?rFvDiU1<8$BXsC<~fV7G(?pjZ6?X z5Wj6)uKydr>$<@Y0R<|n$FlqYB=8XGf&dQy7?T{XvHY-v0R?z`zz-3?p8O9oaI*;T z!?H}Un5@avT%MfVu8}Ot=DWx9y1~v60uG?Sjav61 z!Q#x#3Gl?-jKoS@&K_LOB=E2n5JAQquBsu+5A3h{tg`Xp!Q#4D3Vq4oTg?!ypVw@$ z8*Itt9MIva%JeMA0#U&K`^e&|EU!$i8w|%t>vz$-#2ehtdHfGEywDLu$l1Ai)1R%@MuS);ZB2e7?~<$RT}k9Imt+5z4&FH4R*g6~K$d(h2YY=Q|K((8D9VSSB6Gwj8la zjo8ASoQh4ts|^ti9ocQ|)|9Q<8(rHBtl8nJEHX{f+#Jex46YlD+G@So<_rZx5Z>Y~ z%LA>!aLmKTJQu-S&O;E$Y&_HFOUQE(%?|Ce!+qb?S={ET(Gx+`Y2DCf%)ph6#LJxk z@4ep)d;oZ1Oc(NGM_r40cx(9atD!Xfa^L5;MoT-4a>03`4%ziZg| zo#IWsuk5*MdJD1C?b$M}(Omq$7I3qSUBK~S;%==M(k<8*Mc@7m{;i-L;N6|pTJ0|H zY_7lT3>FZ^;X2Fn39T+Yyp)BqdYV4mr)Dds>Ozd|m|9SzB=oGxFyzQXL+1Ca?9K*YwaOtNgm4iN$n zp3&>f&HU{UXq^Dcg3*S|&gD!Hs?O@Vj@xlu*!HdI$j+Ob&fHYp#h=XUA&?0*izH+` zuMptE&L9FSEbQj$%Zz-&L0|#?TIe$140h}Qvo6>Roc=D+e8#GR#33;529C-P{to~S zcP{LP9M~s*?8zSR3d-Ux0k`*SxrQv~0gLGZ-|(He><6#2qK(AGZtM09^hiIKL|^okd-O^F^lG{EOy9Up5A{|*mQp|Ugq+O%XpLz`j(3Tu~+)8kM(D-NtdUk~DsO$a&mY{_@|VzJEsO-*?%V|F^h}rDXi`|Nj8-PvAg;1q~iVm{8$Dh7BD) zgcwocM2ZzHUc{JD<3^4hJr30H0R@a3Kun%QY4QdP6dyEvZ0O9{AeuF8-o%+x=T4qI zef|U*ROnEmIc35`npEjhrcIqbg&I}<>Qt&#tzN~NRqIx+UA=w<8&>RCvSrPlMVnUb zTDEQ7zJ;4Hy?y@%9$dIp)OA&# z#M^rKa^}sQKZhP&`gH2mtzXBUUHkUt#gC&*u9y3G^5xB+N1tB(diL$zzlR?mxA*TV zS&NsSU;lpo{r&$3FhBtZB(OjOFQTu$@D60KK?fg%FhU6@q_9E@FDx*@?-sqZB(X#jPc%`(<2XdIMHl{GgfT`LXQZ)4?7e4HlR*`egL9*mn+_%8)-MhoyJ??kTch5cdykicA(!U%% zo@cE&*L>!pd{U*VFklG%rbOe3#;Z){&sd<$;NSPOxGLDPK!qjFhgX#?ExAB7R9-bl zlGd%LK#jW=jZgh%3u7UC>ZAHwHNGLsLJgrQA3jZyB_B+Ut&Jo;Es2BqLM^Ftv@lt* z4~=}p}dVPb-;Lhtwx?=_5d%mPrW%r`|KyJRA#8nCA`1%gQ_k)BM%jRAf1RSDk3E9{C z;EQevKL|IlDqpbB5=`*@Al0#I9{HqM#5&g1%n4JEA%*n8SAXUTo1~E30#v}p>IQqK z(HVawIW4uqHY<<$n=h^?&%G-jfTkW5_f1`eePNfP*rUTYb>`pSej#~&<&yxJ$)n26 zX0a#L9`{X9bF04^sZ~iY(|Y4*4~so*BDJo1+NxbtV;?b zmW`G_pvt?g7M-$+(pdVnLj`x;v^RAoX0^-Mla{j`cK1UN>hKx9;bI*JjVa<`BUgSr zcp=id=J`^bzoh&3w6q%c)w~u-kM+`}8jsCd45=4CT3Bjd>~twfz1$nJu6=nhxAXKVuX{rTbCDFm2U{~mU>b){0$^D)XPc%!~DDw;06pW@ISuqueC>O}O0rO7{F*DF`mFX;`> z^LfDeKt#>>_(u@osTVnupD6$1R!2KHEvEDqiUmnuRExY7Z+N-7m0;iJE*~rY6cG(u z{l4fSsFh%GxyECczAsZ~*1}C9nvM~DF-!8+q66icE+u`jn?Ba!3nE(XgMD8Qpw^Om zC(>7k}`#cjmb{r)tmE4JFcqPmGB z1L;Z%wmRVzx~YN#8Ct%!x+$W1nfe2n22Cr<{F5Ua>d7B4*e?GMMkLU~fST1bzw*ehcxn zw`3ACYS$kuh-|XA5~wulbQvs+yYk3JLCm-}VzB5LUY0>Ec29rFU~!)BBl`zpCZmIc zC8bS|9y?c>Ozsbs)?Rt+;45Z2OEOf}qVU);ywY@0aHzb?_pwuo*qs&qp^BlV$1cT{ zcQ#yxDyObIacdGY+m0BjT2gr8K2T}4UqV`z<@@Btg4o?-#NbfPLDQ30dzE()`$M(o zR~)>qh?|2+hwCsD9o`UEnWGC0*F*dqe3-=VVH*rLkTyH`2~^#~cO7n|L4O*cAZ|ez zISge{eELqW%7UbHxQW~EY2X9#`xHaN%|gvjgPp7HQy&bsNTEB1`ifi9la911DLQ@( zud-wo8fnw=a|}xnf52`q(r(b~7*SmH;D+l6>@NDVs3vhM-pCQSwc@krfhsG((vglQ ze$QeS#H~e#Mmk-apMBk{vX(p;>GDK(ioYUZBTG8k?XT#RNL+2BC^Xs=;^&miB=Jzy zV6-=~*(p_^`k|)lXkQ$# zbG4n>!RSyex=X&VguMmn*l>%YOF?+Gy_L||NSB{WQHsPPTZ6IDp=Oto;_63_UB||z z&|S-#Bpy3Pj*TxVx>gKSKXxe{o7nVoty++H;yyGsdC=@yvseA()dBE$9Nn$%iloCE z((!2wCAS9R8V5h2@fnD}8 zHI5Ob;|tvW&*2XwpG6OiFAB9h?{uzt_Vr+VNeaWg+gH*lk#u5NNy)u8yv8Y2XyUt; zzk7d*q;sah#ELRk+v;U-C!g2)NPnzfS0=s=B2N$JG;6Mv8K1xeS6 zp@|JwK<3=5ajiL+*!0ABF?~hKt$}oM%U|imEOD({v(V&^5dRnROj6I=4JNlETV5;* z)IRTYo!p7Tc)6?~<=z`Pxtpf+az(G!eXw+LFVFww+5;(%(V@xx(w3JS&b1zs2a^Z2 z7_YW`rC!XEP93%=z1j}1eX%Gsb=2kmYBxpd<%+@7@leaF{o>k}8?I9)Qy89yO;WG6 zBd1Q6lsu0IYG3V_PMvM~d!8;xc^(f#kVVp*z)K_|##B)nO&qVHefmK+t3gb@;n=K)5IYQTMHRhq3yHG8(CT*goTZ~OQ{7tvzn{;)X^emh7otg}Mnhe95jFOv-i<(TJ zO{V=#cjlYScAM^^HJcMP-(zgHK=3!;mv6SzZGK?cY~|E!?bB=%*8DKJ*|wt51ttSj+R|7WbkS4`|Da{+5^X zEw6T4JkeUch+1DWw!YzS^_Fk-(QWm$Z1r<$_4jEF2y1_AF?7c7J=$e0%P0dmb7rp9uDi z5mvwtE0l*7>B5RFVI@wm(NZ5+Ss1K58CFpQtAxU;`eD`cu$o<1EgHOz2wu+!Z{UYF z%EO_$@Fq)mvlG0<2i_V6Z%c-^7r|jrIJ_U;F%R$Dg?FKKbQ5*-Fn09vcl613^y_vE zSau9Lbqx7*42N}$BzKG!b&Nqf#``-a<~t^LJEqV&r-?de7&~YAJLlv(=XE<5EISvS zI+uJpm%}>0CwHzCb*@4?*ZMox=Q}rcJ2%m~wuriZFm`S8ckRe`?do>zS$6F^bshM0 z9foxsC3hVcb)7)FPW!vg=DQG8d0rq8-4PlJI_Mn4>4J%Yjtjy8rD0=3aB!~T;a!73 zhzSVD2norrU8B5qjf#lqIx#UdF)R8&(|R##EcP*c;? z(9qJ-(!O=;*6rK3b#-<1^z`)g^$iRR3=Iv9jEszpjZI8UOifMi+___BW_I`PU2}8u zd-v{HSXkV@f8Wy5^1*`#R#sNl*48#QHV+>@w6(Rhv$M0ew+G_IA3uKlXoM_kR9yx`t|EK zZ{B!&d;9qK`1<<#`T6<#`v(LBynXxj-Me@1-@gwG3=9ei3Jwkq2?+@e4gK)p!^e*w zKYjWX78VvB9v%@95g8d76$R+^pQEFrzkK-;6B82~8~gR^*SNU2`1tsQgoMPz#FUhj z^z`)1%&hF}+}zy!{QQD~f}+C0lH%gB($b2uvZ{)Tn##)B%F4Q`s`~2chU)6Zni>Gm ze}URs=pWEjTl*7rbxoJ3uWxQ>0MOFd*xJ<8*4*6Q(gJI1gSWSLz~Nn;o!#Bty}iBt z{rv+&L&Kw^qvPY_6B84Ylao_ZQ`6JaGcz-@v$Jz^bMy1_3kwU2i;GK3OUuj4-@kuf zSy@?KU0qvSTVDsZuWW2?ZfjJNmmv1pPe`Ll; z?2qEUCw*~}Fpwun_=?{ZRn$P_Ng~F^0YHKS02cr$E*=>kJ~=*w0zyDZKtOr*D%DlO z>x6{YuU(@CKtx0XfS8!}62Bg=e|e06L-y-Q0?rU46&2I<>&!GXEVQ(&baZU=^y~}_ z9E^+{OiY~2%v`LjH`v&?IXHN@xNdTD^YQWu@be1_3JMDgi;0Ozh>J^#i%UsJNJ~n} zNJ+^7kd~I0k&%~`m6wxKke63bP*7A-Qc_k{R#8z=RaI3}Q&U%0*U-?=)YL>CB5mX` z($UfRf9)hCB_$;%Cj;jxH8nLYE$#AD<>ch#<>i0-hCEe8MVF_lysWIUyc{@K)t6_h zs;aKK8hN%Z4i@rk{pR#soR#`!;H=a)v;b$Np|KS>E{)JOD1fH+rl$7hW>|9zyrs3H zt*x^i)&=kA>Fnz3?iuLo8yXlK86E*n&Gh8d?DWk141iex3zwLkTb!F;T3B3OT3-3S zvbMUmv9`XovH4?jYkPBRXY0q#kL_Im+dF#zc6I^m@9ypI?H%mxAMPI<9vlKk==k{f z;t(NE(eIBDaHxNt_N3p= zGV2&+dFZ*PS^8C@vSfYiGuA4zx|ZO^RGl}@`Kf!p$W$ZJzZdiPNBwN55YoTr+)`Nx zK`@_H9BfWEA{t*vy4@|=nC;8D+a*bV^m2dM2(`DS=IrEPtfCJuxfYn=UjclL{k{vq zpnL)nd3H3xeL3ET z#CAC$fH`wHF;H-2IVn_;{d;nlzU}vvD67oxsWC1q-_zp#*jLh%BWzbP(o=!do9vR6 zl`O;`4ScJ4b?6*x`A`zOwQsG=S!)Gw!PT|GZbgpuqJDk5_2OZxto4#{m(}&sX+MsQ zviS(Rjq>Hxtc{AblGTmMt!9qRs@*}m&FaI&tj(I!{ngD{5C-Q~9U7_qRy`(5_ErO~ z(AribffDDBKN$G@Ik#IGBki}_Sktn%+c``BVc@&;@9lJoAFS#BcFU7K+U@zB ze^1xo(cXZebzASc^jUubGBSqC_Uf;Gl9Mw40NSpL&I_m-jMuL- z($capFt7tP)ijLMFQI9y zfrN>M7805Ov`qnM0k~zVed~_4&YfETZr{{0tE#H1tE+2jYHDk1>+0(2>+2gD z8lX^UQ&Uqj@@r{nX>Dz7Yinx`W%Nw zvr1TQ>b&5J5YqBJgmbDAr}@&o){b9C0%`ePTpnly!UV1JDDXCy`?4@_%__uK1ZH6& zwaAiTOQ{}7Iz8w_nKaEZ?SSHISOpH8MWzb-*)pp0ksJx}xS2YfP9L+z^#>IOdSy!*>@A=mU8-#E=o&(en~ zGiS_8$%q%WQy5Sc&WGvN4C7o4$Gwg8e1BhvH23>|yJUb%`tV^Z6{o!LrFLJ;M|)`FjW0tNHtSMbG&AyHq6v2H?go1qRz}s|ALd-OmJu z8{bI?j?_iJ6dbM2tQH)rsK9d_vh51W9&izQ?@BibTR9Ese!*eKC*y_jsuYG!{Mc+jQ^yPaZxWC8ykPE7(lhSC=~DxBoXiF?duyDK$ZxAP#~%J*!aZw z_~iJ+Ga)WupJzAnnUH{C|8j4d zc2b2sF!2UT2AfXKMaeKJ2Uv#7!{Jx#u*^Fg{A*H00we9QC%x-qMNT^_#19Xq#Hu_{ zaR!&SsHW=!t}(2sZH?V)3dP`i^=19(Jj{vwU<~&W=3G@5Oef=`~sn%-uX`%R}Z)9hfvU9Oj6$5k(v%FaP zp>0oBr@tXyy_X54zCkh5cUL3PUXPr$y&vYUYLAs>xwEr6)m2*ySOU-imVriMGyNKq zIeq#KE|O&sD6-D@5b4{@_>v)629?Y5j6YB^W&;=_Y-ZmAEMxW^XUQ_MWU$T!@(57#sPOcr5e%SIM? z$GtpBR_a|jX_R{6!$m{ei1JiZ+tedwlc((>)<({w&zB53c9AUOxO~Z=p9sk^C`8i@ zCisyoL$t!iU{M!f8R4475n=$#U`vbs_@FC22_d(Vk?_j5EAx;5x{^hi;U3~C*N3E;L=16>ED0Z%GWP*A{NFe)l48XDS_D_79b(J?SE zFfp;Pu&}YQad2>OadGkS@d+RhLIQ$ogoH%bu8|NCk&%#4kdj^}C#R;MprxdwqoSgx zre*-7O5o}P)Jg^hHbzGFOPPgeF=81Ku1$kM@vgbTl@B{Te>rtlsEx>^S`mW`EPH_ z3&9g;s*5kWX`q$9?4yAuy1uTip}xMcp`odpl$-KcV}l0AaM5d^!4@i19vOXfDa?@)-gb@0R+z9shfbV35c42 zmU*dS0utuo;o*h&^5+ufKMR`w^ta2~@*Imw`DcSowA?k-fP7RbKiqu}iVbJB9arwY z*Z5wV*O7|6XST(k7~#v0=ewHtTLLZ*nEb;Rk`*GiWu zFRG0IKIUOhwV|t5WGhQn25=cF6-S>S@xuF#D@XB$p;Qi^cMd%OgK^(z_ zZBO;ovr7M$Zo9#Oq*IJnwYWhk$7xah5 zdF0XQ$_r+N-Mk`*is*k&7-QH+tMuCog%)K5jT=P&?-@ICXhK5rb7=A}Nc+EL?0>)g zVr2yyubaRP!p{$=y5eGDfS4;UEv+acqkJ*`P*YG)S5(wcQqojb)>2W?R#Ca7s;ct~ zZmX*P9%^d0{~EgL>PTp4Afc(Lr=_K*t*x)4V{rSnp{}lxzP_=6fr*iksfmf%ojbtr z1eluuLz4&pH;zmG?S=Bc&Da6D4)k9abp4X6x3{¥ZteJ3E1yNEfmf>+J((B1pnM zG%_**&~;!c0?>6}8UoBgfC&h2QLL@50;7-3bwJNWGIpS&+TPmQ1_mJ8fS$X(x3jYk zU>DGHcMkv{+o^qIJ9W`f9bc-t$H%9a%I?L41gY-+mZkspM)}Fuf75;i(6!Y00vF1u z|4dxtTQWW!Mz>o@O1{J*lBdca&6PheseP6I+&iZlL34K0cSxA3_?hqKVA0#Bsp_%V z>(20~oiu1H+8kbN)K1*30A;Ki6N+ycx3jR42+@?B=?2q`m~2<030C#|u6_F;VY6>< z`1T|>hiiCnT_y+Zpv^;xOG(dH_-d}aHIM&Hqu)6gz48NX1*jBzF?8YEjrzJPfObWa zG+o>&lqH!{c0@yLdA|QSRb7|f3U?hW&W|p)CuJM&!F%@kK2*7{9$V*Lhm+9#H^b%x zRQ_s;c0Ey7_uWd`;Uv~w+?3F^>J1)-mg)RSE|SV3gc7pboOOszPVk;o?Xse;pG zmmyeO+$7>v$;8>vwO43Y(Ec$Z;srIKflwqsX|h6Xe-m=T?Z1P(NV}pKOi3K3Eb$X6 zlCa+Zt|AFnm4d6wz%^t$H03(96gspOJ8vm<>L_>KR_WAL?b1{0(pT>?)a*9W>NdXB zW2W0{ZqRFK+HYe%@W^t|(RRq?@rc{g5f7))7tTnyKO1#*7y*9&*lNJ;exKFdZVTfM zb0gSYgSI<*ttPjd3~x2+Yu4XZueqgCsi|11Dp#Z+nI$cfB*y*%+U)Fg#*ruw|yVVWPKUq_bqCyT?FlMo()(M`J)stwT+%ew|8@ zib{r(Qi6g~gq)I}jDm-hf{T=#la!o;oRaH04G$fo027N48;2MdkJL>*Spi{1F$pzk zS#3Fm+e*rS@@S~8X{>eYj*jkKJp&6vBTExgYcq3Oa|^rsmXEBg9Uj^`+CO%F^u*_lShRAS6b!k5{En7M?Q`Aa0mEFclPkObiCLh{$eA}I-(c#(g@!9DK@&fte9r8;%LWIdZ6ih@!&-%v^QJ^$k zQ?Th4`mY%eiB-fFNk20l_@G?j$c%?02u8<6#)I)yjIJbQ=VQip&+hW~Uz;?$rh|sMQ>nZO+4@uIuN1Dd$`4aFTc*jO)I>+~=*9rO+Xox0080Un!Jc z5qo6F>_K5f1IA|>_WMr_27=t-e4cadXrz+ihu`_vPbY2{#)#-FB?12v=AG)mP5=9QXbQkF7SPxR=*@ z2m(b>N0NehT(9`ixKF*th<9VZx&V`2f@}AC{7~S(`;zeOmYM|fp5q7hndCyE+oYKZ zz7$6MS5Z*4eqc;{UdP@8i<~Fl?~*79jEaAYqsS- zP;7sn3W;g@0abthZgl&OD?7-c1JKI-1e5`!@U+Fy>UvIe&Z#$kbr&n^nQn|17c1MjlSRF ztF(ZkM=*KR;f^wKT=TU|wFt8nD{VpP<&OBH`9?LlbOK>*@!bhFwG7m&>DE93>obk` zYrb+i5=HW$Y~=<;uf}i&Vg@jNi)NN~-FIBddjH*c!ul7sxI~Vx5XGMq#Sz)}6^&8* z!g4D?b({3-z4*t3?fm-&gV0`k$>8%O{b&#<|hI94BSx;9oj0W*C_cuPcv&XP>lTC$VKEcCE7E=1 zr;e6hbQ_lq=U%<9^i^sl6>CB$j7t4uTGaC%PCm`x@Zxd5+c(>SMS>9S0ov#i5^WAOa>}C{STUDtHuUa|)uemyEMbMdF7rUb=YFvR#^`Kh zh*>(yg9bh*HMS9SZ&~9_plPCN3}#pgUZYQIj6#uUE9Y&a8)UM#G09T=a~|;E(bGpY z9}+0Ek3ah0*<+lvMj~7N;*_4A2p-q;)h|flbcT8rrI|92>?^~{x+xCA-cogN=ab8s z+*3Tdn4ZOjuB4H8baPWe_6nO2^HvbXU=_2xy*9tGMiOa0$WO4@K=lb39vNpH3W?rP z$bIont0G4mG;{O!cAQM%yXE?5Q34PT+=i>Wfx*|-M`&b?HQ%n(zrE_o8|hgEqw9cE za2+xc*jYN#y&zPPjQ^qHKkcQ=%%*hx3zlf!wu6jaiJ^h`PV_;>cn0}cb}3KZW` z-WSa`QVR2so#=+?i)w~a?*6zY^7!URNDs^7t(|D5FY)M7$J@pJ{0M(X_LQ!#_|UK8 zbX}HTyU`t{_e&8Xeq_s;T}tS|&nv>=M6;pz)6~5*QDxw#zF0$Jyf@1~Wq_$m=1k>Q z>aqIpR_r|7P1iIRv0poEJgS%ROE|Piqhil{&h*Jw2^1ON9`(jUfT&FH%Hx`k_oPXV z^YND?>2YjlL+%Le-UX?pVmq*+KOU0_;A1kZ*ASn-cAb-)H9!UuC*3Uil@3?2s2*FR ze(OyLjtah3Gu9n~M)GEmADi|&Hg>C!_pbIIsFr+Mq`wA)pc2i~+gY`;sRVuqS|ejj zLgP^rbi5byf-p!yq*Wk(TLs(s0j;Y7j8VnkuwreNAdm>o_>EZT^Bq$h{y4NayXIis zaeG4H79+uDsf8*wHy6cLnbC^63st+HzNd(nZhN(ldqrE@+eBBln*j^+qKTwO`iwF` z185u|j^AloNIeS+6B8E;8z1{(l8lE(gojUp4`Ah=3-l@PEUQ4wCdPI!%) zkcj#k5j7Dp9T71jF$oh12@44+8z~tF833TWqTryQ!wy)A0GXeO ziIs(gg_V_+jg5_+ot=|IfSXJDCbudd&uxL5cLaGK2=hG>;dd4lcp)z6FCq9rQYczV zC_!2{OPyt;n9rYMqN8J@zr=ot z`5F`ZHTJ@@mzbEElAN8E`Yj{9Br~HTJF_+?yD=xLH7BbhC$l>zvp+L+C^>EmBVI9k7i~_(oR;65X8}JzX2U-5Y(q z8w33tLxbz1BkSX18wve1O2O zm_vw#{5vv;G1|v(b8-RHa{5KAy>rbUuWKbj%RVr+gyWdBXJdYfhkqf3^n$KFm=$ye z_mF#Efksq%4(hJ-(|6!7zD1xbR->bllzT1)F#9xxDhPOv8Y;t!^~R)JioFO3C^I)vA>8yowM$mIU!1#e% z{oQ^js_P7l>L&Y`(;+L%ns3O{o+KPG4|Fbv=-Onx#xt>{_vvN>dL3sB=ukoHt^h3e z#vzUbGtZaTD6W{D#tCI*+-Il--A9czTUJJ(&g^x?<<)~ZShAZmCwN?!g1hijHm=3+ z(O9g3=Fb$t!*Fc$ysiz9B68WHip}yaEdO%*Rl1jYD^A>3Gb=LUB1+`5uZqsq6MG zM;#z&AW2r1(-f%Ry~$dIKC%VIqN?6{PiugQOjxRl&K zA|W+5EfY|${)4`}yxhF}+`MnO`31T8g#hxt73UR{au zV3oAzRk5Ece`q;PY$-`U~yWt zfNAIOFDI~u*Ks*brDgQVa1E&R$)yFxkrP-te{`~~nSVKfoeLI6ns%ho+2#SC=fn9A zNYljQ*X^GcCpk-*RrRV;M&-1Olx=p>by}5J!=XA~A`#f*Nk(#I6qcHqd zmy@{qJD+D(8?g2Ov*?OG51jI!eV%YuT*F5{wAhTzQ@k5Ue(Uq(EfY0QQDCF;z};@= zANq&SQ~f^dY8BNpG@3ZoysqE)Jg2aWy02f8c-H?LpXd1+FIncIkg&@VxaQD08oCah^< ze+_E9u{SKoVqf}eP-EriFG~3`^My;l1T~@#)U2=6kv=*70R%OQj~CxnJKgS9;y&B$ zH+b@Nr_2Ny)abf-cF=5bF8)hUV_w=}Y}V$_phnq2vzf!)rHi0OZqM~hySQ_7U^^a= z)7X?XF`e%th$g!AnjkP94MJMxtw8JL12D6ToW|~lWbf-p%+d2OTX8&Te&#gZus6S| zj0!Ixem#TL?fWyQ(YlxejIj4IV)J6+M0d~i{~6S%s>cB)@fvAXy`n%Q_~V@+Jdva{ zipk75o(Rn5n~?7)&n^)9-sU2xQIXP@9Zc+nN+zWLjzH53Ol}VZHMX`$?%&kL{A59< z_A~+0a#_`SH;JCKtyQtWL5+xTo)+sN)jVs9OcXqsS*Y#TphhK?cAe<$uypkl_FsY; z15-GEpweL}=zRQS`8xhp<7G}GZYqzz3fzKXC#qFFmG@^(W48|jj*l=go&ExQ4?I<{ z^k+^ZZklk5N~dG=PRz1;nw2PnlBuFX0G~MK?M0O?_tBlNhX{C@&1FzyNyT1{Lr|9KlIl>!=w7Z{N0!+^`%umCUY-|T zwmF8{a0A7Det<@{1u1N}S#&SWR3LBd|-Y>1g%XRQq8=s{(xCm;DgpDtX z9+YI-%kiW zgYR+JNQ?jNGN|zo%BuP8odJB7r1G$dC-_^SwfY>z*kQA9=eJ;2_#E}gVT&|=L8!m_ zJU!)6tFmUn$4K}*v)ECac5uN(PNT_DyJ2TRL@9jXhQ|@i9KSHCMSYR?%Msj0vk(zI z1YZp7n_ z=&cPz$ENX1%UXb(#xE!1%bKMXLme9~l_wKh!KGD88k_E8CzFSrr8Nf~o3Bnzra+Lg zIt88<$9d0nnNmwFM=A+)@-yYM~6E1ODfOS9WH_z4=Tpa zHr%=@Cl5LgYECYL8ZopE8z>Q50a{hFq+N&27eS4uVR<)>+D*)8!)mJ*g}B30pCNYM zDOD`VXdO2QN$jL(RZrc@J09eFwVN9vF>&u4G73ZN+m}i#KJ7XwEv(rgJ%73Bf7dx= z_3L4)R?T)KGNG?|aAu#ddqKLs`ZC*?FE0-^%ml?4P2 z2LvAmgp$4eDEv0e@a_K}L5)#Cqh&$k!$FgWLDOWxv%q2 zj(ogv1mrX#{#H<2?v+_+ljT)kRR_8<8lVi=f7U&r$C#f*OO5qCzi%8pDh} zM_dFoMwfq%xd>{ECy!3N$Z1S{9-V%X)0jOHoqLhfSRnGH=pv`F?DLn3i=4)qqc3$A zIgL=GnC6R|#`f|U_(e`*H+gLDMNZ@3^Vne^rx8&eJ3bOSc@fk&EAn;zBB*is^VgN~ zuWJ`Uja%e#+dxpGQQZD>WKiRAdE6-w)OZvJqKF5A8qtj7(Se}G=y>c3WKbjiaXbMK z)F_%jWQ+`IB#Ta<0D>Av6R3}oL5=jHiHtx{qkAH2G%~1>b2RY=5Y$MK#4CynY7}%& z5(a`AE0V-VkwJ~p6v?tcP@{3OqB}CEQMDpj9mr`sPS&PK0dg93jZ^f2oW|%B;|gR> zquFtaIgryRnrdl`%xScVPPGMc8b?zfA0u-b9YxcejFCBwZqaG(Ku+Um+N)z^PU9QV zbc7EO)aafb5RD9K3>-}l27($XGCqnTgBl~;GopZ?#)=FdTNF|+6pDNl>S>hg@hIdD zD4mTN6V@nX-6%|3nfVl1d7~&~p_!SsFSD`11r=G9(I_SES=~z+)OA@<_AFZMtU8Kp za_ww5dlto3R@=K5B>5;E#yR=6#66iWNO-abM{}q_IV0>IeWN+^#^7O!+%jtv3hms5 zk;W|C9W zQ1GD?%x#y$OZohG8uB9)oEHsV(S{s_LUwrYS+&87p%6qUK2seEMJRZ08nOY(S~Q0o zY2!0_ffqrL{b>k78?xe17#dk9W>>UbS8VkKvg!q9-zwUuLt(4KXEVnag%vXK;NP*s zSM-2b!HSPTB_T@%OrhW((~tvmeAZC#<}~Ec9G?vYLC``dyYUeq2vsO#->Z~q8vmJH z(T}>a*Eo=utEE9hU>(XL)sswi9*DLHcx4*0ACHpx9zr1v(W5N$uEdv6E6uaTAEB)X zibTz4FW-v?=d+hRv%`1KLUCV(P|%jIn-}eAmo?epuYw@b2oJm(m>Ul$E4Z@Mqq2}) zyM)5Lgu_{83?@_x4f{168&%ufp>}nTj%Wv6L z-VwtWx5Ix*Q2V|#|E(Hi1(vlC3OR#Sz4-!JB!CouDOJJAYD=wqYmon)plDe;+fJ-L zVYFyT8?x+sb5}enkDk?L4}|=L^0#W$9*oO^{tfE3Awy9)*f0yEUtjJrq2~ z)A)V}9clud6syY5#AnVfJtu@@zD3B)ru$hFgwWBba|y+!+d4& z5A!CrQ1Ib2WEiLUU0Uu$W-|g-yu(u&QkhFxmlx$E?iPD5#|>WPUWGF6k^9=TWxPGx zcf5ATtJQQh>;1T!H*OYP9SR*m*0u2lgYg=!YzVP-Td;MDS{4dRJ_=Gk#c+J<6_m$xbNP7!I>qZK|_>PU?Us0xSMrhfK%hyO+%(ne){w9ki!uUK%CRXk^!f2usUk zE^zmK5HMZa3JhKg3i%%T@q1YK>X(?U#N_qV^wo^a^~|iTtnA(F?EUPVLjbwChu;eJ zDy!BSq04RU-@AG?hR60NXAY+qkLFiS7B|ioH~$h#n`ggbdGqWN-#5;F#p>qC`sVS* z*3rg~!>#QD79u>Ze^bEg6ceJ~7-4@SIb0mw(#@2NGAFY5kM zoZCb`!UkT{mHo>lD${xhh>>h_e?3h7%!r{+r0U;@bDBbEyfU@MyZ-6L#wtgLMHpa#rbjct>EeuRyiW*r!)6Dn|gT$v2T{_Pf(o8NHXt8K9Q zYR4qE(fsxic45F*V)|`qg2=#)9I>w7Ji>O$6qw@F?qsDI+3mSlqH_5N+dpcz?3`zU z?~|7GuaB_PI@SjnR!_4>9a^{!#+`;99ZWnc2;xJu-Mcm-Gzn}`Ih^((eOxx<&fRe^ zbdB%TuUk|$p3dIkMwBeX{M@1rqqEiSbz47tMdfA0IXTa*w zM(Tj&Jy-oxoO>}^i;4n@yg^WViWKL7Eh-%2r#mK!ac2k9{>UvV43!d*+{$+m$7`iX zajy0`B7!%a=KSpNLY(UwuEH!{jRRp^igO(vG9XB69Xe8+lQ-L0Gf1vg__;*IuoUY? z=+7l8vPoy1rp5*4SU;Dj{CE@IQ5I3isNMZU$mi!0m3#w)v~mNkQgZ-z`j(Q?X~N|e zm8;rck7OIm_8~9J>Cnel`>y@mqVnKPI3-c?vGCv6O8>xc)?(3ryutE03k4eT{XeJEhlc zL`Aer2G4X_mZbStaZYro?dKAeH1VasCC-WNCJ+Rr%d@Edl{iOiLP-rpDJj>GUd9zYpOR1zY2-(2;@{I}vQ>Tn1yi~+W&h>l%uQ31p`$&;flFZ`l-42@-3%H!?; zK%67(SXLA}2E@6dWEPF@swRLq*IAS*1c-ANTU3hEl>l+>%W;1?AkGyVn1(6+VR$_oS7hF=5rm=2o0*G^+z!UzC z^~WA3fH)@oO4T}G28Ssm!2i-DvP7Z{SoY8+C_C<){0QVUkd#MY*9Jq zt%!BpOf@~F;ZQDzTW`#V_nj^ZFBOz?=f8=%8!%2xqYQfnY*Ar3T9B*_YzXSyO?^MS zc#e7O>(?0u=yd-<;@q|3&z{nbz!sINkzDz&5ImXfsIJrL8?^~cHoy{<+Am97{Kws2 z5W5dKBvybWDlJcI_VPns_M2@I@wOuN%UGhBNcO(yI`8bDnX`I~&zCt-RMu zU|MaIt6Rt=D)eFnj3x!l9tEsl3fPfLR1gI>P70V!P-v&Y6qT+wyij<6Eh?3T;$wx9 zCxr~}QTR;?ovpFycnXM-;$)HfSdr!la*K+NSh222vA##K;g@3L%3}5RDAXJUbmrLd z9Io<*&4^r-9(D0J^p#ORM4nBF0|t*;UM4y6=cII@1|Ak zEkaW5c+xotoYg8?aa`_ZfeR3o1vFp<)Mi+$Cb5`7AQ+UirqMB6%kTx$)Q7>&FysDmvUWn)0Xi48RHMuVkBJ~*qKI8?|2${7fi5O0vK zV$~Rj3WA%&IGYsGpnBP?25V4(#3oa?lbJo+Tf$~Tbp}h*<~!<5Hi4|RoXvc_&5vWA zS=%=~Q*XIx(c=2z8R9u^vj->CKBiIWv_+V-^`UsH4=2N`z*gS0R{w@pz>pq-+s4Y$ z<|o@0G~VJz)f6TEEZUSc#=g<{w9SO7okghK=M75Ai-xo{h-h~E2i%r-v+a4=_20x{ zMKNt9F|1`VFq|rw|2T}XrQO?+K-nB+$pnQ9gnsKA8b>||iwB%H4dDm{fk7ZV9(2SO zc=-gy$sBK_3V1Dx>f}IhzZ(q2ZEqHLY~^Hahr`K2(Xj8~api-^KeDs)NZcNBmL4xcIQtg%d|(Y}9oke3h;tg^1OjPH zLq35(cenaTywDKS;3XL7W>5{zdbco_qv#n+{TdhnLa!eOk#M1sP+gDC z1j_wj>5r%t{U2n6%WR^HICy@#Unccd*sluK91#pqBaeApAY8h0MT!O%VI&acP8lK zF=)+E1|>jPATTZ$Drp@ErHX$3&Lo=aahy78SIGg788i;lV<9Rp-TyZ;a329?Uq> zL)mlx7j17D6vrB9YtIZ00R|23?iwTnhXBFdLU0Hc+-2|q5-dRQ;O-jS-Q6X)I|Lmr zd+&4hIk(Qe->>^st$M3#s%N^pTB@ekTF*O+GyxB2M}TF(1MItqBR4TiH<5&wJiR+b zd}qZBRuDwHb%uY-x7rH{)AN#SXR~ev+iq)>ZRxabDvov^d;q|S0``ZN5PiS}#nZsj zS$_E)GrLub$erD$5!i!IYcnYpH;t=Dr|>vuNNG(?)@S>5rxej(s*0m)<585%MtIEg)7b{fXmGqW z5dV}7E&5x-p)Z(h?EDn*S9Z!-=)(nD=C7}ZznIGHoYAV>&@Khbu|0?Cc`?735m#9r z9>4u`Mcz^8@=$62aCvTCBU4Hw&?wPg(PE%8U}7+0ff;bH>GAPs30~6@5&cE5BPXMxq@<*xqN1jzrlFysrKP2# zqx_FjR?&9W-@n)-e6dNgw@tCP zO>?mQ>0p=PXqV+^m;IN%)21nFALN<4rNn+EX>kP40l3 z{=Ur69ynmY-%zA%Q!uEsqaL)WdoAr|tk>)=g1X#Wn;{Y4H*`SiURu%B=kokCQ!qyK zu|JevY5H{(Y3-{-&L+G9C6bO;1!~zC=pI)h4hVMW(|}D*<5{;)ucQ6_IT4pabee)$ zO`wQo&sR^`KDVxx$h)p@w@M$$Yu94ZBt`+5w>DapB$jCuZ&@Y5hK9_o3+UzUJtz_42;|&;9i6{nFjT`k#mG z^QWD&|IqIF(~I^l9}cf>PcCoHF0ar3rt1DDJ@Mw^>K|QST>Yb~%NOwOUsm4B!hh*s z7w;bc%!}#v;;cPB{+Ds~fAGQns?faX@$d2fa8W+MU^g(>)!*a&-z)v|BQH+e|F>=T zvhBZgcYk~L(%<<@FAFbJn*ZSt{{6v=q4?+K;qKz`;ppjc2llk_^tkf$u=sR8{dnK| z^w@TH+kA7`aC1<1y183nuvmX12b$v+{duX?1;RW$gvf^P(40&kGLiAI;7!OwTRM_Af6r ztPNEy|0_PP z$V!Uebb{wll6OaPKuJ<$Msj>^L{Md*Tcy|M8drl3FUp!WXE_7k8PJUh2-tFmeo$zk+Mrbb13X(EsLmhzyE8BMzhjmEWn})J z9KCJXY9qaGSGcXEGMJ0~30#vxbk3J2#ar}?Cz2m?yj7#0=;fZpy@^q01 zveFm{?XG8K>uGzAE92=R+5Z-|UzsTzn3oaYh97OpJKX5UA?HKWqp=Pg!s1hU!75)L z&R1K%Ffy6XrwM+-=5S9hnv{pXXS@BhYUQsF-kY=dsub7y_+N4R*xJ5?&p!*1)|JM` zCDI{Fw)Tr3wt{gn{G}B3z=25r1w0eq($hMPe{C#uh-5jJJWMhIiV zE%P}ESE;}`rcEukNej}FVbqn0M!cF|1lPEcO&1lD5_AIt*{BM!s^J{;V#J zS#wTu2dRSC#MT_UZHh~=o3NU@J|BSFQAUan{=q8upEIhI()XYis38*TW7!*uyrIU);c7+3B5`Vd-YWiDEUMOhD8Vt6 z5;IBxJmT(>=R3ZdQjXYm5W^U>86QW{IaZxuk&+~tLw}N=3s&*BiC$4dd-^PH&3@%3Wqc{Gj2J*|>NXvxWp}ro zm_oxL&Ibp>t9nffi(W#FvE!B)LAGdYAo5tw+Y99-M+a+bNazgX0U8Yw=1+LEqnwUh2iLCYaJ4-2z6tD)NZr}xVoRl9+P`L?@fKJ7?C-e!H8nS3GDZmP zg3-MqQf$9b4r}!iS*<4m)`R}|7feoS4C*YlR~*dY$eh1xi9-1^-3PfHw=#dLL%W?B zM=1=wjYosD+JXrV%)_P$q|7z?-uj+Wy4xsVA?ScFB)K4|uLf%f7MhLmUiJsoY?dap znzivFzT#`{H#SQ_X7la@SsG+8I-y94_j}PD?tug4>|AcV3knt%L$MRQ;UC%#9LH)o z+}n9zd^_scKJWgKVI*#4E}Eo9;(5BUowi1SoUqkp6vS?SusvY+TZ&L5q@9q|9!CAW z>Yup1(rVC-8(iV~D10Jr*LpRT_>l|`ZB=9N8!2@Z1x@-pelRbS=?_e9sFap7PSR7q zaOM7nCF{-*J0JC$9KR zq;yHnSE{)>b)f|+Uzu6L3p{J`ps0g+MKne#?Lg^xqTX*g&6Em5^f9)tM${O1p&ED_ z%Bh0z=G!@WbVh=+COe9`Q#4_tXM*5jHY9l@lA^!k_U6F}Z{--(>lBi9{Xiz_qGH4o{C{i^q z8rjx;;9ByFpKJQ{cii599}$q~ zgcezj5vScGqJ42E+3WCn1Uz1LolYZH$1KXym5=d6^hmrsKaEP|Mu!(C+A&_ri_rOT zPE&R%Uj>zrIMJFtkFg;C+5BD0DfvUC(O0g#gqXI?cRVx;Gn>3nv!`8U;iexOH!@`i zpcCzsI;VpG>jwIFN1iWn`>4sqk#&?5y`^qteXF68QP!Bujuh(Pg!A^EVZ#}C4+_>d zRSd6Hmxt}VC_G7IyV)@;ldS3}=Uc5l!zv=$!i6OXiT!x%HBNS&Qxet(a^1e~>J7$A zq{cTq^0vOj?bGwE0(i7|xQI8tASf12T|9%8dcOSP(irp_5+JUhqk4lZ+nW_0+ z;hV`nbYR8YQ*DQX1@!c2jw3fk%j~ut`!0`hFlxGs%UoiXS;UVSFX9EdZrG(1cZD&V z*O5dDj79Qn1#Z&%xqWM?T5{9Ffr9gC`?m$CwA* zwCUFXRF%yIk0cjcu4an&_wPQxg|W$=t{PnT^NsRzinykv`2G1SZvQl;hG@OU6li4Q zl_-MsIzWgCO2vOy+=d%Y_LI7&-kOoqr%}^QtkUml({X#wad*x^o!$Q7%Jt8To7d4%V-zXYVc~bsXW|z@~ z?zpbHd9on+((pkx=#5}n5b^v#689jP#2^GmTC_cH9?Q?a|A5nfh*SN-$4mq#N`0mk zlR`7;`Gqyb7mSJHPp2K!+vl_97OV=zjWPB7Jqxic2y)gAb)6?5n+f9N$Gw{* z3pw}1&v(r=XHA_Ac0@LWF`kFYg+htW^_1MjYXbOI!^FylK-3DL>NZ**XjR^+oND{0SO@Jk|C?wkvr~@ zdx?<}*U*Fc$dlX1vwnYM8G$2$s2lgc9KH58QC9^~u-hmAVKhj9{+d4;(IXl;DH=72 zAf+xE<1QLZ7=!KM&ZHKN;}JuU6hqVyL$VMS*X%X_a zh5?W+l29w*Z_hB9GLp?bQfOl^pQDp=7Ek5t1yF_v#A2TfkCjc{PUJP2g;1FRGD^~I1kwSLFgQ~`tYZ*CfO~|f7iT}zo zqvoTl6vu}bhb03e1i>BxsOQl@Lgfq}S@4JTe2`I&Oi@lrQK2VeNeC)GH5d+41^%Zp zrdu*_=H-Cz;zt&+028v&lzP1VySQwW$yQ&Gu@nvqM~cTOw{viKgJw%xq|no{LXTD+Z;4|10|5n z5-HR)M*G||aOxkqwE3b^=8n?Ydl;saa#g7=7)~&=(X*Juvzp&p`}8x$=0a8KVk%u@ zPOC=%@p|EwKa6zibz>!kocGJ_46Y89JOl$*dZ%s8I1byiI;cRJ972`n{^!L3_U;!rCEsE6zq z654|_<#L`gN}iSLL76`ut%?n;tDYhCp-uHZhgFc`nn1KdEfmajW^h1Qp)7F&$iE@r z1GB|LH6a*-sG^ZD14N9LI5yD;;!HgeEs<)%rUWE^BCZoBsx48e@$||-1J?I4*TcOW z$?w9(JCvqyaJ8~er#66$3I{q zC8umf0BPIPkvmYx$6KVBe+26_$EN^S1;APMm~lfy)Q06hi0hI+}DFN_; zYu?L)gjvAz517;`P4w;wYXYd_g#ckM41JRP60Ba4=J?`!%uLaCI(_(9Mi6UCEwX(f zvRKZ11P}%y&JGX+vwH<(6qWyh0Nc@3h^8s0(} zot*BSl5vLqUcSVErBIJC?Zp9T{XCE;1kPwDazCoyicU@`0i~r58rY;%$PZ!~#lLg` zGMM@kmIi;M4m7>$XJhJ@FD^GnX^&JPu`m^T8a#)@{ACgO|A4U{} z#^{bxu%B{g^nQLE%nxqvAW2Qt8tP(a?L)Q!qwDssE>CtB;_g9)jFz9MdOcZS((^x++j|NZHn(^1+tn`Psz$h-|9KyGSGH{v zE7f0)w%baU5GJ-4zOAk{#)}G1h@T7(hLuEqSj;S4>rYONU&)Ydt$<-v?(U=R*vM~d zdSs~SFEFk47zl#jvi0hnRE-mk6UQv`+O1_JZ3_tR>>n>pHdMY$8TueUhW2(J4sB1# zt~6o=TztHyCS0E`oSb2>-R#{~i7}^b2adE(3oF~?YR!Nv0+ z`xNBeogi22VY_c;15pXJOuF|UNV&KG0;Vg1smv-n}g7h?e7<9D?15=5d?xmkfwEn($#60mi z-}c@96M0Ig401XIOMy{0&yHWSf)!6@-I9Kal+Q1()F6u`5h-2aB%hT|#83Nxr|pjC zG1@*99Y}axxg}jKDeRoE?ZicH=Vzc_iegTGZG2{(LUowg^!P#S1!}P?6(X+oExr^g z--%?~v`I^*SGpKp{o_l{OLW|CInY(9aE&X{%3TA@>qyD?mc9+SwQ9PxX}bxq?HvEy zocO6`C$bRH?~>H6stC5-caWxBsg<0~&I=)6c!&5=DyVDS%#Qf1hSPsTg z&{vqP@^|dhlCPf^RUVNrEt>q8C;}Qoe^ZKK5OTFc+}Yp)h@OQ$$6>`Thi1|xyLKmQ z9BP(*Z1F`P);!X#G8;*kO3^<+QQe4JOt)(QkA)UhPTIIyGqZd4eI3XrCE&2BVWp zr)pOkhoMtm@ThF9v?Y=~+j`H-N+4oScniUBp$lC75wA?Fc8&9%ctssu`JUhY7Wsx?9~CNu}#;I`;3mf^a;@Nd{}Q|=rV)Nk33+EoQTc-2-V zZBbQIgY}V;I$^DI70KsR(}EAfRdct2DsRZDFV4Fts>9E+=FLC!G2lf0J{Nw>JNnjD zN~dOo;*ATHE5sQd&d6iubx>a@dfO*O={L-s# zXKo?(!P6#f2?TVwypX9&D#tg6<;<8WPE*BHDl~6?3&cUY(ct2M(gr6#J4t;Xf4zHXaen`U5dGfe|}nPAePrWFZvQ!_bwLK>gRH;^OQs-do=|e zv*`XZiLmhfE@~)-5jIdRluGcW9Mb(c@6g@0Z>~7taEUPv_0_m-2z(!tqd|QZiGwfF zK{HgTJiz;^I>qznzXU<q{m(){Wy=oMTIRH-U3!B9NqR9Uh=*dGyhV*K4SqWtET*3@u0Sm1}tsC&`Y<|X;X zu@lCF=p~I%HGYZX*Nq3WH|s8tUEtpEhN(Z#w?{~0VQw#TIOpjqV0`J5_?1cR!$h!5 ze^%-5$V&(BBhkYz*NzwA!bVNN(4i4-$bQL6NycvqjFQHRo0H$eS&RO@d*y`jDGkG! zbRk8>a)ghA+?dQEgHdwfhSH+G^I|kujHm}I zR6jtMFDRo^H%pgc@p$PFNWk+$9}XpU4H|2`?#$_@e&acL=nx^p^`GdY1!X}v zQySlmN?`U8rgzVvK4P?Gs?zwT6!%>yvS0|RGU(!mZuk}rXBfXfvP$YZp|N72#8H=v z^0ZT-L?LqVu~+ll%+l%>*gs^7>)-8>i;B@muF?t6i>rhS4T&Pz@TI4R-FJ!Xvs*7w>Lj&w&d(n&!K-@CDDH?}99Zw?t$VJpZpwhua9q_ak2@~* zi_7obB@8QeWc?9J6sV82M^~>)h#*#`z#T=QDu?FJ?bxbg9~KDye9uC zlDXnc3G&W#XM^<>#m4E{lRs4YMtBRe@MQ2-P$?l}>>u;p^wJLD8qLNfDA&pCANA6X zRAymV-2elH88rrTp=hG6A=^C$RE*NBW%qVR3d-A%vx*IrADyzvnaDB9Z#Y0sJ2oV2 zq%ugcB?Hfp{{GZgo5ysj+jLL%A=>F|@qSgC{!l~z;N^2?*$kc!#vX8*9kW{GAhXzh zozL=`ipi9SsBbLVM`Rl!gBZBO!L=U#2%FV$Gd3~PMc8H83r0E;;GGD-;($TjL0%8P zc`4++KlOw*IlsBBZXl{alF6~4{z%^X8+AWrJT=__NsKh{Jp%gXj$8bd1q<^V#1$4D z_dCA$7DvO;)0N6{H8jilGNSmRDb8=ULThGe8caQ|Z_-dcYVzgRQTx8SNVDc3w}KTD zcl=~2zT;xG{{4BU%*&i5*X!8aP;aY%f}1z7big>^_3}E>d-qPfR{IdX<=;mB!#Oe1ZTO$BjZi}p!Z7tWh%#=wwy_N%b3%Nv1$ql&-AixxT?pZMw!KZ@bpeR*usrPKT4DRZjVic{|j zfXoWT^=W1eug$%eZ}mJX)O!nuI{ML=A~lvBZ4GJDZ8-GXg);ipHfAu!)CP>>Fzvn1 zfkvC>Z2jsr6M0X3e4qs1?$dR#`m5b9Uux$~l5W^5o_@|-9<97yyX|}LdlBE}y~DS5 zHx}x9l`GL7^gAk66?s_7u^?jV`& zAl>R9yXqi^@1(%(q@?Mj;_0N8?4(ieq&4lNbMB-M>ST!PWX$Pgs)2PfcXzT(cd~AE zvR!qu!*_Auc5%{laq)Cag13+m#J>k`Q660GTZ+ubEJ-Suv(OZciw z1io7ow_A*+Tb!p`Lb6*@y<5t(TiUt%eNeYdT(@jaw_Hv4hwg6q>28IsZpEu^CHNj? z+#VI09#x(mHOU@z^&Sn=9!=*St)L$5xE`IH9^INAz3v|U>7I{UJqA}jpWu59aeIwu zdX0H{O(c6w)qBlMd(EADErNP2<9a{m^jg*QT6g!_O!wMu_1azaeu3|^$L({V>2u`i zbCT?HR_}8$?Q?bRa|`NokL&Zu>GQ1V^Xl&Np6>J6>hryNITP^x5Zr!$n*IQu{y@q8 zAoc!W)BX_W{?MR)Xk33-PJejK%b%S7uhaeCw)!Kl`lH|nqHzaeXa-_=2EI!U#HkO& zn+_y65BvxkNQ@gu${9$m8A$0KNSz)?+Zsr}8u$r6n1MT(Ni&$mGng$on4>pU{&Ebk^mR}9Cb zm=SIsFw#hY1TLstz8dKwKybJPV7fyw_C}<`T0AL6{q_JfaFun@BjAjY;>wZIc7>XP z(L)>Dg&OG2BO3F=5V4o4SX`)BI<8pVKPrPY7Qv5|Fr*wo z0Q@ZX3eiPbRwuv?04X_BDq#qI*~-*Z;q4I=%N7lM2fw$Rt4}*wg3={I7Yu%hA0ytc|ymK}=ACxZcHWXVvcpIJLQxH7<^`&Fn&ey_2g8L>4KC4`dFdV;Z&qan zv<2|`f*(XV>lpBZ7HOMD2a2_rqmcK7fF!6#Eir3;N1arZFc^mvQr7~-Lx6~|LhR+H zBNzbtq6k+c5YdnRM+^wpJmjkUD!35jF5YQBQ3M=~F;VntRsQ9Ovk*TDKo0*{<5Osp z&!UKtCbUqIuy!#mw;D!E5SlHuK&Yz-ogect8}((yB_RbC$s~qTPI}mDKohl`X1-xO zg~E?0@*nG#j;y4%EMj&=F>h*t5$1M!P%W+(FvUY>e4rHH6g}ZU^w+}M;s{X#{Y%;$wlmPG(yfNaE#D=&*D{XxJ@hd89;mQXYPjNlB z5ejq-KZ_r|2!>vw&mNjJc9!2ulDti~A_G?OfXKiC;LZQolo?0=u z2<-GNL< zg#Z8|0Pq#+!&!#g@%t0>N7AxE3Pd%D2$z37A(?*6$MYEz%iMLM7!7i;a8KW{EZp&I z*|9;KH@GlZ+usf`Fxx3L*-!^s@J^|JSJXz|7Q)`@*w-A_n4}8U04n6+$?Ui$1J&PH zYuasf^x}EgYwdj9TB;0Pi_e=^raPgrSh_YN^;)AhI1+NT!S+8|V4SiwGctS22fGhi zg;i|RW{e%kC$f_U_mDAHXKp*T8ZDWbOJ;sT`>n3-3MCzxiU^9kx?H4}TJ=s28 zSF8;~4+}Xq$JQbYUR}JNIbzZ1DOd)A=>|wm>I)I%QkEGt)uDh{)=20;i*Y>=04mT6 z?cY!24^|eie@fWMpWXNSb$TNGRk3cWbccL54L0fufZB@??|e0bEbDX%p7B; z^{7{oPO;D$O;fe_!LINaibH=yDiUq8GZB*+E|WHyb-eQ@d-uSRaj>MRdqO0&6Q?R+_HYdE7 zjJi(UcdB;2AG7VuAAItAq#duW!?;Q->BBA&j2zALcK7oLqT*3R{9;MbuY}6Kbf2wY zrGMHviW0AfN_?Rs4UH~N;N-#Ajess-;qCW~ZjGYHJ&c}G8Yt~+2jG$VUwK1OLO%oR zmlM-J!({-UZcqkzA%jx-J$QL)dx-cm`j4(Dg9=vg&zPpHfOqe4|L`iVj9-;9Z3)(2 zEXU7gq&X54T!>YgsQk9m=+$I!n~`|!xGjCUhyF3Q;c`ic)CqQbW54UrDIDq9zsfg@ z>byH0S`i(bU`Zl1!8KRl&VK~f_P;oSUi)q<5|7)sXtBTFe5ZG`L2{|+HseqpLl-qV z`g`8K6>k~_7UA1M5Jb}Ys3w4K?)8kb)-rQ@jQrE^PL9e$mG90^=IV*m1=ajCat<}Q z?bf}B27dHYVA>v8iw6{{^X*h6lFv=W4{FTlvR`4=;`{Lx`PYX$EOl^*sZ21mdIReI9x!;f zd^zSWQx^B1$>n<#m-g;dxf|hI0k+tQ`70k(XoRY$E-F2p_QQgXf%{SS=8GO)>7EgW znl)vkHh6v)pLXWi;sk9GN&3$>q@MGXJdHreN@TFYTMOm57C&n9M{W!I;Eq6)*F<0Q zqm3k0E)Zdd08Xm@XsY+IET1n-yX_FwzFysww|}K&Ln-0E`GNxRV^D<=1E@FT0pg(` z2GPx*9}-s)YbX#5A!zY$pW_F003lI+Oi4r}u=ww}a&YgcvJMia*25|p=BjKXCbN(U zKJ=sK+(u1>`q7C~J!)bBmc$L;1BLE zCyxSGFBAT%mD@cD-W)B~JDr}?U5hJupv4A#<2Nk`qJ!^M6l%@!h|M57Bo%0*wu;?G zI= zV9gQ*VBwt)gplbVlUqK=eSa;*kX2(G0b{o8&kRKf(4G(}ZqUk=$BrbO zxS^6Eh@{z$Bn^yhC%Y6$iwn###bV{eKn{3-|p9Gda( zjPIhLSgq#9#*Yi{$^Db^?tgwIa|Vwh<@k_YirpR1GjS zu~d%;KCskG$h~E)ozYF%=f9JZ{4MSiPiXcvKb!(1lvgHbDegDZ!lBHbC_V_yB>XB@ zObQ>%evli<@Q%J!rrm|=u#(U0aF@dV>40(^g(uoG8Evhh!8XL6JsHp1Az zez|=NQzqO>_*kEWt@W-0xm5BX?E46c7OXpK@y>7Y&6<6Ea*VNYjP>+RNH{-koh;!L z4oZI@>exiLeLv7kZ%i6AjRtZB(d4Jl=kNg1b+2nI(;>!0v08A6fOd7Jv@_g!wmVZW zlK`*WjM^GN6f%Hi_Kii~I`(KwMEE@kPfxlhw6Z~JlX$YX$L?;70VOx-d>l%NZW4>g z>w72y#4(@TnF|*d+|Nz%6+9>`ZWTN%ZGw3oQbe4g^JISg{<;Jf3GPa)^Q&=^d~60w98?wa z$s~6!2Bq&S7smTnUL+?0XEz*eH+(wNVknW7@c6(;Ej%J5O*;_R>wOk|A5Oe`Y<>}V zN)F}^+7yx(eF0Asm>-drR+0`Zq7&1K;b@>C_gntS$mV7Nh=m!Tz?&R-Af~<~nu5Vq zpNRA40ZaW(g&0A@0HRbz_NMza)T-Dpdr=d4;1F?&@;Q1SE(CsUzL)RlCRRXa8{xxx zuTI`ZEijdWIeG{MsNNpvm(fYMcD95IXUGs3M+HCtK!Diw@m%S~;qPL=nXnifT$*8G z_I^2??%sNZk1&?dpa}}3hd+D()M&^n_0*4vZa9q`{n}B~@nURYlq=^axS z?WU=W>&%a6XHnB+lQ|yh3s$0{59!p={wSzgI-r%QIt|f6xGDArl&x%9ISwJ;-@sILtvmOXOk=W?X*dU`sl*l#>pTjturY;ajL*K$!??wdRMc+e38hC+zVZHA8#C;h%<=p2qSRO z%DaeY>LSd<_FmE%qk}iZZzz^bXBP5lW6JB>TGr5y&A`du*sCMZLuX(r`h)lQG|jmu z4%}Q(T4jc^G++A}y3chwXsQji?Cp>KglBzPtVqby&yOl)5>G$#_9~fuK+W zi`6JWsS_~xJ$s}bUqw8Js4PWLfPlAJ5sqrtG1Z%xOD~<7jsFP*dF|e>;9)bY`coHA z00cnPqnt=X1+NGJDFDwD0DxxnFOzG9WKv2@A3f`}8DG`m*DLXgga9lp*_8iEmJ z!ZIps!ko>a2o#HP&8*)tl~mBVXkN6*Hphk`0~l9g7t0Rk_l9Bfy77Qs*B##K212;c!@4lfLxp(yhJ@OQ(l^$qRH_nv#@|KVut5ozh&a%T$uX7x* zvEXbLW?Nyr6@jcrMmSCLz0#5Nb;NR{sjDtMwsmS+T7|v& z)x~<^@V#kyN0_g1s`SLAx@qOam#=w~_0(gkY4xU+ul2I@)aSBk4F=`a*lkEAW!N_(I^SybCPDEf(t?VMc zx_O(;Phi-D?J{YqdFM@=z^GH%W!h!)uJE(Kcp%$VCQi$qw20v3_p+;8?v{N;Kf&p2 zw(CN*mIKW;!P)Av>r$tdLxX3*`7XAb%I_^l<|1zwr^;?>t6Prk{9tdFH`)F)OtqZ2 zw!K}wEc?@P*>dXp{B|9V{k8+A^(;h0XalGGwuig*Jkn2Si<s6k}y92fI`oUK0M<(nK3sbE(b#3oX{=({9w*F~-es>ng z{`ecG?Y37$_~Lu{<2HBO-Kd}NRW|$6zFOP;Y@6^+b@|h=Q`^Jpv+!*fJM8>>+vARi z$o*6~?7F(`>BLXuag+V|ZmJD-(sLosuXolc`Iiy@MVF(HdFor)Zu?xh{*jm5QY|i)ECGbvBE2m5OaA zi|vGp{U(bYM#X`c&4EG9iJ#3$M$JW+&BaE|{U#g6ElkZLoz0_2&8wNsYe4rOreIHyE^dRC0^1TYGhz!eJs<1AVdm1e|O65j`cO}Wn>D`)$ zp|V!n5}u(VL6T}*eQKJyKJv60pgx@hGp$uvE{S6R!Xq4LYf?ZP0t(Ww|7r$bpyM=c zrt8|L8z8C2)~83ds74e7&(hAP9B8K6#lPOA+L^0IL1*AgM=VT;NYIU73bELu{iOO{ z;AmS#HdMGo(h;0z#1(2`K4>g0X~NWJQrBY}5Co#x5rG5|dfmg}>ENm$b%4Oq6jWOX61Kny{lHj1GR;<8_05_WWbc4boZrn#nMqW+Y9 z?o|2S!kLa$Qg8V!5aZf;MB(`Q;Q}&toTcBp7|=Ur(D`${cdzSV{1u3RoC7b^9u+Ui zur47=)D0I0!05J+^acf^(!g`Hqr$FaB!$eSlTF~q5gGae5riOt;srYXlBVJCDvp-H zu&&UM0$JRSPo8_A3~A3SNsr`RuR1A3U(s){E;t6!<{e&PY=qS{g9CWpIL?ImA;qX zYXwq96@hev3J~%Vg7Q53VcN<0Z_uqJ-3!GaR5&0grb%}% zn0GG*bR$r-YiGdB_3{FpUZzSb2uzFYhLB07F^LKX1uYXvx}cVYKAOoeh;{~acU~}C zaj=*SZ`J9dZ*(53#Rv7q!uMANdj-H}H6qAu9O;T4LPLU_0)lc?kE)gd+MVb+pNL%9ncSR++!l=qALbOC97{_ImarsA2PGjY z9W8?ax+l*;0k#fDpU-t4jb3UqR`UWY$HU})zV28CV}&&r&DB(+}72SV!mb*6R| zsN|aK=pq*YZ5SkQWY&dUhFBqrHnWf5E$Lz)`uJ$_?W$0WgT+ccA80dVpjgoi3j`ev z(I3&S!pv<(XG?-obKxo4Ia50YO03{5hpNcf5iJ2Dv0K9S5q7c0o~|Ir7|)QILr2!3u?VM`hBZWq-Ti= z>F#%e?Z`F~Jh*VYLg&@#eJm3a!up@FLj!ffI8ahJPW#dL0LkJ(WG^`gNT!Qjdq+qcFmKFGi}Uj)x%*jeE7l4Fu=^e>@eBNi-QV^V zGcrey$=EMXlZL9GIx>detHnU>kX0C*Ll($3smq`$XbX`eT{nMM@I>L!)_LEy%l87d zT2}lR$+YS0K*T3ad20Vwu$0pJb5gjJ$*n|IY)*XoW(25wR8HnA-Twk=K$X9M)#RjE z=>vv}#1cGn1ALAYOKnJOvqW}tm!8M6HUOM>g4gz}AA_*BcJUV*L^=q}NZ9W5f<%0# zGe{IP;j(8_swyIXj#NsfJMVKu{PRDUbD`FTIYh_ozq(@V;@+L2mZe%?c z!87Y~mre>{N(f#@0CAq>t&+57C!a~51S!PBUh?JK=Be|-Cd0xPF{8%Awxqc>yhc2FPH1(PNQIH%U+Z!cdb1SiIxGWFt$gex!e=$;4hio`tMMX%0KZAP@B zvNIP?vhsXD~J|q-&QB?vW5eXI4?&v z4cILMoJhE40Ff7Y1}yh{$1Y>H1+>;>x32euLwQ@Y4LODROG2j?@kQ%Vp|uMlLTEqFBFdnoD;I|24;p2 z`tNNxO4KS#04PYL18kb|O!Mn`0J#fT!28bR1*m{N(Cf+Mwv6v7E{pgG?WqKl1XC9+ zFz2nEz9vgTRSKgKl}Co29*w4_6??nsoU zJ{xaUws+R1xtXtdluJ01p9exmM6rrwVcKp>;&o#ls<4wpJ61cZE=p$8#<6BK0#qBJ zuY2toI%|Lf!Kz5$inyYOI7?LL;2H!daPa-=aXv5 zu$#Oop|D{hyI>3I0;oHH4{AifZb-1H2G}sPyKYE$d^5NCI!7dynq;xwW@oFs^s>Cm z`!_U~H`js$KGV0(vpd)Ko?c?W81v^njPFm>J3zxGbMh+*RQ{3yBkgPQD{-=G1_0*$ z@-4!@vM*QXZ!W=oq6O19XN@y3L!jmYaP+C)#A|K|_4s$Gpy}fYAo4P_AuI-yOwvo_Um49 zvn_d;y~4w${))ue){Mj+bHXBdO$&95C%ojcZ3y(~62QahMucv^ziJ=8!k_L0tH?Xl zKS2Bwh(KULg9bTKFc?9_L5Kbh9&kAEpSlSPTR3EQ{*ZwTfeA5EoY?Omiiztk3Y1{d zAVh;K3%V>Q5uw6@5i*K2NYWrpgP|&v)Ok?m&72PpCirMmV1gb7%`yF_)SyL!9f?L9 zdJtqm6g>a21RKFb(541a7F?P2CDMWo#xZT+Lg%S-W_xbcyO(cYzkdM-7Ce}6VZ(zmoaD7yqR-n&!0hu7CoADY15}sr&hh1FulIq_!jw8Ps4?O z7c!9W_|d{Yf(6qFOi^%@yW0OShYoN+IEbB;yy6SO1XGZY zt%9g3M=A;b36aNxta>YkgBY}MHx3O-z@`WQ6tX*}K9o{H5;JV;pmdmu;DaXjnh{La z$Sl*$Gto>_%{AF<)6F;Gj8o1z>8#VvJMqj@&pjmz$Im|j^^?y$A-qzg8xz36p;7V~ z@gEg$%2A+EI#e>IgP@#EM4IG}x!GCW2hXH!Hp=O+4HSYA&iKP4|0tL1iCrcBjbtnQ1EWj$` zjXVBWp^JfXNCFHr0Jht`mZ+33s%~W9n{o1qIM|vTYCwYuMl={+pk@}2EBpt@fa$K=?z{2MTkpO3?%VIb0S{d8!S@_^ zD+>4eFz3E54pM20MP}I`to}ukK=Oa0qR7Epb!wb*y~x%nXuzm~bRx^yopfbPM?@U- zMOXj12%+UZh;oWh|Cwor<}TUnR0$7W_~D5!-uUB@PhR=unQz{CJq_Q=lej5j(G#E5 z+5p_fi}2@hjv}vBJ%eu=IY8P&_SXBYxF^5CxI-6RpYKIV7FG?lPhFt<6LfU?*WD75 zQ!V6N;}b~)%oo6$uza}ZJ>5WoNl(Dp*{r3f8_d_V~l zWkW&kQ6&#RKp+*#$bUEzk|Oy8K5Qa^2AJR}Lj21q#-R^R76bwg=)gCg(6uA+Q5T!&)S`w50(4@FxN46!9!yd})V+dX#%{q>g zoaHp+Ebwpm8eBE>QRxJRHYKeeF-quQlT1EsZN!u zRki9>?@2zVVwJ01_3Br_8dkB6)n7$9SsKZjR<*8`t!;JdTj3g4xz3fYb+zkV@tRk? z?v<~7_3K{&8(6^(mav62>|qg`Sj8@uv5j@?V<8(^$xfECm9^|;F`HSwYBYSahqG+?v}T`_3dwg8(iTIm$=0> z?s5K+n_T5Cm$}V#?sK6VUFlAjy4AJrb+MaW?QWO5-SzHw!5d!jj+eaUHSc-Rn_l&< zm%Z(E?|b1JU-{0LzV)^5ees)L{qC2){q^sE0UTff517COHt>NFoL~hnn86Kp@Pi>7 zVF^!|!WFjgg)y9A4R4sk9ro~tK^$TckC?^PAxuXF1QA&ULo)o$;J!J@1*%efIO80sb9m zK@XbHg*Nn|5uIp7FPhPfcJ!km9cf8Vn$nfF^rbPKX-#jM)1CJ8r$HTRQIDF`r8f1c zQJrd4ubS1ZcJ-@a9cx+7n%1?p^{sK8YhCY}*S+@juYnzGVGoVuQ$tK9`pLT{N6IR`OVi${&RQZoaa3s zug=@;bDar&t0-`M>pN^o_ECBUGIJOJ7D?luS?kDACB6@mkQE> z5QHO=6+}k|I#7Z`7-aArsA4ASq4;A$1@eEKh&v!Z0_b3_^QC7XU%p7#Ut_uIeZ5IRQb|};2S*9VFyGH$JhU(?6Mcg@bx7Ka@4~EkPm?gCQtj)m%aqQ z?|=!!O?!%z0Njrky|^o{0&$B%9v~1%2Sfma;m3aJ74`n;DS~*W4*~ETg8V?B^U_ZsZg2Xk?;mup^ny?K&`Kco@BhvO`3`~tk8bBELiRMU>00mj z)-J9lK^`FQC~jyT@DKQ6MEd%#_Wt4h4nhDsZv-~a0QpY>IZp&4N%@%1`JiuaI?(mt z4FlJ&={)cQVK45AFzeP5BNr} z3;XZ%_M!uk?+1x54cV{#l85#Z5DNJ(_v``kKwtnNj|8aD1>a~0kud0z&|fA&1!E98 z2oV7bf)WVtA4EU~!cF@og8KyF{lKpd#Sh%}&mbUg1^oW-?u?J3_>dyXVGA816ZmiX z8e;!eQ1JlJAXIVs0OSCvPaqbs5EZZv(XUe?uoYK>0*}xLlQ8Qr&>;YE1E)?212L{T z;S^0^A|?U!%J3f!aTZ%p429_gORooMFb}Da^l(rh2C@HwPj8U%7lpAHjj-m{@b!xC z6XA;q`Nath?@Itg59yS3gR3(U;xLl3%`*t zkg*eiaUCbJ^?dOSSx^pfj}|Qv9H9}dLa~iHF&R_uBI`vVug_Qn;S=Zq{saOJiE;l# zfd5zk7~v2JRY(jg@gM!M0RMp!#-S!Fa_!FX?*81*8bu-?2eKO_f%Zgz1tw1t#^LmK zaVRR!CxJ4JZqE$;v5Nc=@|ux^KEMXc#3aj+BGJtcx8m#C5hsDrDBsd8O_Ho0F9bfI z5f369fo~gnKoCMf9v)B{GqNB;aTL)HknAz@c=8`)k%1f%ZBWt#RFWlK@@~#@7>ke? z)3N5tu`T0nF4GGh_eCD%@E;gR5ZvJ(JZ~Roaxt^eAP#Tw#*zd{F9b4AT0$@c1hOH~ zukixo7>Du)JJTX3l6hbfHs_Ed7xVTUB_!EsBq{P2laXKE0V$JmFpn=aO#t-t(I9RT zE-5kGUUM074=$tgEzfhKoD(@^GXzLb{{Fnu6~~en4}v_ok};FhAzt$!DlY^)z&*v1 zEXl+yhtn-5qBu3vK&3N5%`+Hr5;ViA@>rk_PY)$kVDfb0IDc;(Vi`w(_IUFl2Lm__bVb`wMiq4SuyZSw(@{8qCJECzMKU-&aWnrA zLGPt5XK*4kv>@b<1x7Ihd{ZzbaYJcfCr9M=v~fYDQw=R@J;&5Yk(Bc4kOX{BB&t#; z_!L8JFF*OSBlXh~QFQbcwJg>C6fJ>qE;H3nD-b~~&`KM0twymCtFt>Rk06KQO>>er zR}&%lZ%^k?1oiPTb20?l)CaQ^G+lH?do@j!&@=VRM)L(n@l!_+A|C!BMI-3_Dzz&O zLJ|M*@$gbf(Qg;(5UoH@1^CcbAuFi&*i+SfkJk(P3FD^LgG6V2LyWm!oHI zFb23&ATW z_OMS7s#8@9;ydv)Ed^mepD_lHsczS{q2?BA|3Pb24;}FDDJLRiDI#%0Zw6(t^XRbu zMkEX8lrAv07u6IH9duOhR#Rb=16}ts&$eF4>h|{IWBc_#=`&#Ca7@QhN~cyZgU?H; z^+>!GWKU6Vbrp6Wbajh%I?=P>pchSP6up@CUYwR59U?Uw)_GhIW{LD>p`-G4_c51| zPX9q6n=x#sZ~kmiFLupVd6%~+*U=oOH*nf^BfruM_qJw*PhMHqJnI);>aTL^^nN)} z5X|8p{tqq*mv{5DU?p`8XTx(30XkFnqUIKS{c(3E#00@|e@mAlCU_Guv~xF6IZ^RT z|3MN0w+2EuHGEN6XYmt%Bd`USSRfD?_>ehZaM*cG zmWFrPieGe-4OzXqxL&^a^uqKB_mM;AFoF$p1zE*bM{xw8^gHGliv41njdB?6_-sA3 zc{KP*D^^_%lY!=zgVb_f+pwGS#T=S1FyB;KSI|R^vVjGnhhsq8znB%oTF797*U>^ zClp=UE*De!?(nRePgQLd6YN1Zc`}|0qo8-$7!9{A^O<>w8UPzvUH_pv-8G;$=#EKJ zkLxB(@pqATR~19xtncs&A6k-C@1MnObQctS3;LRE*`Nf~tQkA4yU`#dx}*(Dw0d5+dWFYj!!3Ln2_KF&@4FdFV@G~*5 zM<4r6kFP&j01+gCJ;wN{Jb@l!pn{Dz@+ue|=Q>cybQ=k#y#E37U;q&WVuBUfcw5^Q zH5@}#@e55`g(sC`4Vx7waAnaJpIKD4bz+vOy0&$>b+@>x#MgEb+G|g-1dgGC|5Bla z+B%&&{V4n%A#WZJ`*xpLAOxJif6~Bd6JVr!t}k<{=~r!M7`mT3G~w=FzStow*MdSa z$4A7(VYLIq<7WXGL)8}%Z+cv_Ij5&PejB%VoO*qigh=Z$Rjd#e z*rzj{%*~Xl&%CN{Tg~Y=byxSiu3KK>{QO3M8}|1{N8AbDII|Acsk1+w+Ux9@Q?TQmPqA~AY0vY%XHk0eBj%BP(EQE@DTVAlG+7gPFsN7 z{u!}%ITSot@k*cu)($8?VBx`m3Q5)-A1dNE(cUxtjXiV{{4 zHFABAq}O#EwJAXaf8I1wuBK)F*oly?t2r*+>X@?^lnX-VgwtNcFj3BT}{YVZv| z@fE+P5P$I<|M6R@@gaZmDSxCSzw$9Z^DFA|HNW#c|DZVk^F@F3^C|R6|MXFxolHOV zS-)|Mn}W_Hlprd4G9yzxRPZ_=D&7g}?ZXKY7XS_?3V8m52D5 z|M{WcUYtMrslWOMW%{i@`?Y^guz&l#|NGOV`@w(y{K~0-3;p1PdBGh%lkTg$x@yd!KaW1W`t|JFyMGTqzWn+0>)XGNKfnI{{QLX= z4`6@-4oF~u1|EoDf(kCkV1o`m2w{X0PDo*e7G8*9h8k|jVTT@m2x5pLj!0sOCZ335 ziYl(iVv8=m2xE*g&PZd8Hr|M1jymqhV~;-m2xO2#4oPH@MjnY|l1eVgWRp%l31yU0 zPDy2zR$hr^mRfGfWtU!l31*mLj!9;jW}b;=nrg1eW}9xl31^&g&PivTcHW6+o_g-d zXP21l1@r#rIucbX{MTP%4w&behO-+qK-;x zsivNaYN|*I!^|0~z6xusvd((u8O#Wi6fCT|N^7sa{t9fcLfR?}EJvZDYpcR8%WSjG zKKmiDu%PnK5XkNdZMNEO%Wb#&NsG!5)+T!fuil=EZo2BOJ6*ZAPAl%Ut(wbjz4qRV zZ@#X%TWq-F?pm(C0yhRn54ZS&OD+m8%y7dFKMZli4CCU^03(@~F2cEh)H{dL-EBgysAAh&IEj+gB7 z018f3!p{Q>(4f^i57aH9Cm)y~7(5}kpq4ous9;nj_J|MzhhmpaIoVUM%`_$c=%bBf z-2`yL8;P0IfZhLKLJtFo^R*5?-e8^<+%jkVF~G9lj(hI9?{0J^`!w(Z@4^pHeDTH~ zkNoiFRG>EvaO2(l@qX8xeD&5}kA3#qZ(pw9CU7r2CiRd2htcAbZ@3HK8?Cqs+pvg->8pqD8mhB)Ewv5h`ULAV*o2?TPXlw062UubZb;c0}hGE z1{^?kq(ecuP^Y@=;f)`Zq~t!}(TyGEu65qPqbENJ%20}Ol%!lA?Pyj?gn33E zWN@ER^8r5p2~g{ykU6?$Odp= zt!t%a+Zy@64y4q0liY`0_fg4xaD$GzZ467FDLD}#5~ z$o?X4dDMGf-gs%e>5)~koFyeOWj9cbozgoe~ZFPH^@CS&& zM+pEB0H>-|aTU?p0OsNju5DvSB#(zTc<}cOmtY4!^twC0>aHC85G;m|jN~LM{@HD| zBdjKv*8nI(cF7i|ELnHCkz&6Fl+H#cvsK|sMbIa`Am2R`5;7zTb;c?I| z!%ci(1P(N92&Q}ncPy@Zpgx12-T7tSo!Xrq@NR#@ygPC17!v}fL_CBW>sbqUv_y3C z?#giMxh8GZM)NeNMSVk0l$zL;Znm>4s9}1#v6B=OG51P*V(yi}8v~H>8N|9-Pa4wO zj4U18Y%FZztxSw!QC-?+GUMZBDj;32e}h2e>l+-=VBTKK}NA z1aN|R38*N;dFWV#KcEG>^aVT|n*bYhEZ_q_!;=$UF3o~MAFaeL!3pj?{$())CLgE| z4+m5q#xH%@EZ`Q)C2G;0;T@k&8qXoSW)}*viAG!E{UCeAEZ$F}ZMLQe|2KjcmrLkO zG;|jWxZ8L7(0%9dzzxgMfkEH*%YruAsV8x3sna@sIp=V&jh5a)yHJY>T;Tr>+Qqv| z{jkqXSw0-#fE0|492aoF0UABslPg|#GvxFc((CGS&6%5{mb}-6hjv^iP{8hRt-1wV zX0Z>?*G40Kxw=m7a=rbnl@B$1A-}`Pvp)5Uuf6S~oit9E*n$3fPRASJ5e>ec;e=Jy zIETqWRe0EdsvmZqwcW@K_W45`Z#Y9&-n00uCC0wD8`ndE zU`gP>105r8!-o!d#Sg$oM?_Xhl;nVz0D(Uyfwo72CD?5H)?|mpaiwO09<*>0CvXqv zaSumXFi3HE!vG{FaS334E7&m~*KlVrJtq)xf5U?*P~SCj#T(0pH|5 zd1ie|bx~)SQbu)An^psvFb@oMQ4$1H-qm>HH&D*Bh(<$9z~x&q6Ksy7dGrNqp=Msy z6jUIvP4Dzg^@B{dwp5o$Sa8rYPbo zt#^yz=xn%`G;Wj)<)S(MkPq&_4$wz|>gW#c@L}V332vkh@4${)r4I7w5BN9^8fR_l zFn#7%F72od5A|W}=#H4s4(+&(?ht)vkO>Ld{*L-EV#SkU0|06%Rsh7fejPIj+*SZ2 z@D1|fZ)c_q}ZVxqp0(1`azyS>~lO#ZlqnCm3Mr7l_ zTR-*=?|@hDa8*L+I;y1)NC|;J)(P(rmHUvC5or!csg(S{l;Q}MVM$mhSTm93gJL;B zWso-sHv?&iaSjJ^+{HXsww4c|0#<+yP?l0>IfHRIJ$eHJR&WlEMSEz;S?@$q=~aoL zWi)FRb3O%J3?^#K$X%qUI1WWx*XV>D(@l9cP?0Dzl^_nOm0pg=LzU--!zB-ur~ zPOMdC6jcV=@L;KCY98=jB|rlw)N1qo)OIpcnl%7<<5w;U#RAD_G{0y?_TXxYBX`y? ze1^k}m_UrWr9Z%xXoSURJi`tS&;S>}04<<0-T8{N*f5i3nE0lguO?&I`DVp6Wj*vm z-$0ygGn__aoT?{tFF;1Z(@on1Y>?B6Q1_l2C3@AAXkF)1MMXdPWkNLtO)@i??Abfu zRiKo}g6XND8Ricg#x-pek!|D+`m+hU$6egepYB*LZX^z1Wo;c}ZJ6K>_rPBh2@eib z3FKgWk7r`~gKY>@2GGY>=Fp?ZV^%Wrk?J=9XC_uDpg)rU00j^O=vQIeRsaBSLz3_f zE2e)j);8s^0SPF18*q~XD5m~rfNsdMfJvhXUg|e~2sAxG2!xzDm%6l;WW^4?R4#lOsY3WS3=ov* z@HcLysF5W&l{i{q#+WkWnA})r2dY}~0Gj9st3gRxfTl^J)<@!Fs?Q@}O&6h@$*Q($ zG`M=2t~z$c+A&>ML-X}hu9b*!RxY9CjWQFAx`>;WxS++gcOJKCL1}8F2vrw^G9Tao zCqS+!FrE%jp60nQ8#Jn1W=zbQs?hkJX$X$!fKelqok;_&uhxe0fKe$FhPjhoQACOK zU{ZXjI|X~8yOvWlr2d;p6LSExt(GWSrdL$R_O2TXRpe+i(S~ir38L}Vp^|5!T=SwU zYLNx$4-wgJ{@{S6XJYg8qntT?JCw4-^GAU+b$c{dNy?GlDpq~er056$CjbDylwt-z zki(N!iF9KIn62$lra9R$X^LY*$u$e;V;)*jURgdu)~4boGd>oP^j5ZaDnnq)v2E+N z)^n(wm8fqkJeT?ax%7iFxTz3VSr3JSI0dQ?Kuek`gIm@ym%u!jMYm2RI3z%v^E$81 zDr|h#jlIY2nuQrqeRneH-~g)gcL)$^3`2*2=Ku;Y z4uwa(F&2vsfC7XUcnh<==$Z@Tkc!@^0@Ag(7}j+M6}hpMou1Djd6kK!VhB(z*-uk8e+cp?WPXN14nJ6< z&d?I6*`3Y1%-owYxFE=p1;5N1t-qQ;&S+?zAX6*QpKU{H zxJFJsBtfh^tl(;`h)6>-m9cebXyl|(o|bOb=nnYItNRK!irLTT7!UjVYT1gM=ym?i z>DebbJlM_k$s9Vu4lB~}^wl3toLo8SQmP@e&4wFP-&R@}vG3bvWh zfm02rV{E5WT}eh(#uACud9}h24c3I(#%9UKVaya}Xq2q@}vmZD+-5M30kNn2pKqtI1!s=awgW zWw5>xmb-+Oos8+6W;be-sCIx3-W@=hz@~ZUV~dPchXP!N$tB&L<;y5z%>Sn_&s*H; z8odnj+prki=!%N77>f-cczn`ncQV}E=D>uV6${UNK4_*azY>e-vP0=IvvyJZ zgWY;NlsMYO>00g`n)aZ;%I>hZ)35aUx?xD)#{TQ$o>?9|PJWWM-l}7<0%$yPD|7kiU3)b4paQ& zO&#Pt3f7C}(|YSr$+3BTs@^Zdh+@*6I+dWov|TEvbErWiL-l{_r&eaP(0t6ty!R24cs(CVwkFU#=CHp>=f`e7&M4o)%l4r+-yTrs66$%p zJIV5}tHRs)Nn^mp)aU9<&h+&73>Nu{@A|SM(YpQz(z-{|ANuYvdeJ=O54B&863)^R z9;EkBqTs++D?RTTo^1k2{M)mBL;J%uox}3Gv^_miJ&(&bB-F=fuA zS<~iCoHMqAIUuP0ExYrFb1w z$bop3L=CAOO28@W0+T(jXTUCP8d2;*d;U;U^7JV;?AWp%&YlUSkf5Y^aXWn=!It1T z4=d7+cv#Sb2*XnWNAzIA@nXGaD#5Lbsa{-3M}?j#6A!b>P-qFhg*piuS*x8{AKiQg z6FAvAg%5{$E7x)4$&r)O;K2EF=LnADbAUqmadr-*ALnGD`tfzzcja?%V7dba9n`O9 zasB!&;_>Crr(fUx{k^(f0$odx=ioD@s;Z?*#4*gT4*Gftw5UWQ&A)FPJ7Y1VUee@) z{#r6Et}}r0jHL@RglV+U9*c~iaT+8jlMg}|upAa}J7bctPJl>2g7|anKiP1^??)hm z6mm!+i!^e$b^PfjrAgHB#~*GW{wm|iZe+}49JZ1K$1x}U0q3hHy>YAzbZ{W(No`W{ zM;|jGO0pe)vVjPbHQS`*Nj3@6#2;;+<>T)C5fX100RIhUa z2vlGZhM92j>&GON+Ld?SlqBlt0R#>j=nPHto%5iMT42Z_h$Ny&w1)1@*P~-E;^5wk zQlNKYio@mWrwY&T#Dfn0=%FWGbC&X0A$UTdrvnk_sfXj$F0m&;g5D_+BAG=csN}lN z$fO>TRYqCG(bV&pXQ6=Vn4S+rIIXUbvm#N{i&x~?WS>I|Ic1+SbQxuORwzTM#vD{{ z1f=PLXlIdEo*5mE$^PK3f;mq4?F<)#G$o+0e!HmAsHO*mxX`A0WdJMAsR#G2%MLo| z7-%6E@(|cXaqW=n?z>#NqtyahkBdBU(2wiEaMDZn_G*t)_jt9SsrG+?_S@GfNqwG_-=BP>QE8JW^Kr+WC*ffWe?IZgAMEfFL;$Ei zIOXYBYjVds9#W^20MJ@)TGKlS2oDIdrho1DQbh8%zbAEYIGP&3qgLXmyG;TB11JCq zz5##$7$7)~A^=woH2_x$hg1#VKo5IZ0)de9$PdPvXn)TO>A)=?|6qkdgT~QY!M#tDAX7C5ymm{ z16%InV;}waM?ey1T;)1fy4DrQm%QsFh-4&9JYj*0l&W70o8-Ry14)wztRV|fmBAo# z34BEI1pb`xSHm8F0&`$;0Xb`AE7cdZp=9g~bNiE+GbDc=YVK_-z*^@LyrY$*dY)#8f>*~Mvjfm%SyPJE1 z{#}nS@iMb@5(E_UC@4Hdg-my<$EZm4(Ts4Zo}Y@1s~oy%UF~{Tnj~p{`hW*D;=qzh z5HNq@;DG@J3y*1NDJ7NAEB&~E8%gN3PiM#=M5v^aGIYsHe^Q{%Q`Q-37dd*;){u@#BPMZ)O?+Y$r&z@+W^s#MeB$;N6232{agh8Gu^Q*Nk^F^; z%SOPcl=v9PIu26Eghyn=g^oN4n4EIR)45#8V*}1EwjC(VOOUr#<~?P={L7qbBu`>}<&RmRiN{f*8D1 z{py(@8rHK;i4QP+Yh33#7e1hMuYLV%ULM_l3)zpKCresOJ6T;m((c*i~d zaXLdB2)zTmaVUH)>I$6V$!50}Vkesi45yay>)xx0OV=9~vz=tC!Z z(MN7`qbFVI2!}%tZs7}D^c>T;@Wm~9P}-$uUF%!tde?uAbgze9>|-Z;+0A}-w5Q#b zU{`zF-TrpC$6fAor+e1fUU$6bUGID6d*A*3cUs>a@Pj9O;rM^r>fk>s|kP*vDS>?OXlq zZGU^*=U(@_=Y8#Y|9jvEU--i(zTbUceB>uz`O9a1^Id*?=SN@q)2Dv*txs*}Utjy% z=YIFS|NR$dAN=DdfBDUSe)N-0{OMz9B1?SFs#=fC*$*MI;0|9=1s zK+NMm0W3fRJU|3Yz_cSk1#CbEd_V|{z@sA<_d+!Zyg&@hKn>hLhGReu{6G*4K@l85 zPV+z#JV6vpK^0s<12aJud_fqDK^dGu!*M|xyg?ky{y`nwL7lKc9{fQd3_>A%!PWZ* zBRoPROhP4GLMCiNCwxLEj6x}#LMp66E4)H1%t9^PLN4q=FZ@C<3_~#-LozHwGdx2y zOhYwXLpE$fH+(}lj6*q`LprQOJG?_Y%tJlgLq6<7Km0>L3`9X3L_#b?Lp(%8OvEZ= zFA9_~b5jXLj6_MCL`tkgOT0u(%tTGxL{98PPy9qs3`J2KMN%w9Q#?gfOhr{(MOH+@ zmSDS9j73?TMOv&yTf9YF%tc+?MPBShU;IU23`SubMq(_+Gh7LNRP}&iEP9>W0#LCNs~NDluSvLTuGK}Ntb*{Iy?i2gGrjKNt?V$ zoXkm`+)1A7NoABtk+eXc97>`rN~1hVq)bYsTuP?=2cRURkvxN@oJy*!N~^p|tjtQS z+)846N^|)KRO3pq980n+OS3#nv`kC2%)<6MLik!sxQt7=oJ+c_OS`;Fh8(0MTo=6T zOTYX}zzj^m98AJw#c?T2#7s=ZTulDPY)r>|OvsE($(&5etW3+iOw7zo&D>1R>`c%6 zOwbHX(Hu?EEKSoqP1H`mYNP2dbp z;T%rlEKcJ*PUK8Z6}jLtWN8^PVCH1?c7f8>`w3ePVfv*@f=U` zEKl=1PxMSr^;}Q(Y)|)mPxy>a&!h!qumkzLPyEbJ{oK!wv;$?Jg(P$Z`m6%}EKmbI zPy|iTdaQ!~bcG`v0s%D!2USoCtxyZSPz+T@S1<-zaD^fGhawmPR-gr9Foq00Q4~#4 z6A_!4bBmPhpy-^&^Q61e;I;?^io6+~8PZQ-)A}vxQJyIlvLSy(( zLIP3p0tx#8Zh8Oiv83odG83tvD1{=LnGA&axJ=5~6f@n~NVQA7B z4bdkp24;|kF-225y;D5R)8woIX<&w8=+JX{Q!j-DD5Zv3&{ITBR7G9X*qjAw&{A4( zQ$dw6S}=xZkcMZV0!H0bPVH1rrA#Wg)MwyOs1(#UJr`CWRAOL;XqX0R00t_^f;9M4 zTCG)Ez16c!gDju|V33Ashz4eOQCNu4E;UsrO&3}y24#4LX|M)s$cAjd25FsEYOPjl zy;f|^R&Cu@ZtYfY{{2>P4Oej;S8^>^b3IpdO;>eYS9WbzcYRlQjaPY{S9+~id%ahD z%~yThSAOkRfBjd04OoF4Sb{BBgFRS;O<09pScYv_hkaOxjaZ4DSc9fnnXhG>umX`lv_O<9#)S(a^Cmwj27jaiwUS(>d`o4r|_%~_q@S)T1# zpZ!^&4O*ccTB0plqdi)rOLUIK{6^0U324!Fdzx`Xl4P3z; zT*56}!#!O7#7$hqU0lX(T*rM}$c7-CpkPUhn;0@C{$_9bfV-U-La*^i5y& zU0?QXU-x}q_>EuronQK`U;DjZ{LNqe-CzFgU;q7I01jXQ9$*44U;{p21WsTDUSI}p zU-A7glfuyoCi39$^wLVG}-K6i#6kUSSq)VHbX3 z7>@p78J=Mpu3;O#VI0n39o}Ic?qMJPVIU4-As%8PE@C4-VkAytC0=4CZek~XVknMc zDV|~~u3{^`Vl2*LE#6`-?qVFwJ?qfgxV?YjMK^|m6E@VSKWJFG6MP6h^Ze&M(WJr!=NuFd%u4GHT zWK7OvP2OZq?qpB?WKa%eQ66PdE@e|bWmHaORbFLQZe>?~Wmt}7S)OG;c4IcaWsr4a zUEXD0?qy&8Wnd0wVIF2;E@opsW@JugWnN}xZf0kGW@wIPX`W_ku4Ze#W^B%8ZT{Y7 zZtiAp{$_9vXK@~9axQ0cK4)}JXLVj@c5Y{PerI@&XL+7ydah@CzGr;SXMJ{NT<&Lo z{%3#=Xn`JRf-Y!-K4^qaXoX&AhHhwwerSk}Xo;R^imqsjzG#fjXpP=zj_zoW{%DX6 zX^|djk}heJK53LrX_a1SmTqa6ercGFX_=mBnyzV^zG+YNcLkrfzDderl+WYN?)Ts;+9QzG|$_YOUUCuI_5D{%WueYq1_{vMy`0 zJ^+0{g1>9DPHVMZYqoA{w|;B5j%&G|Yr3v$yS{6@&TGBiYrgJlzy52$4s5|5Z2rP7 zY{Ncm#7=C*UTns0Y{!0V$c}8uo@~mlY|FlE%+74h-fYh9Y|s8|&<<_U9&OStZPPw& z)J|>HUTxNHZP$Kn*p6-4o^9H$ZQH(W+|F&?-fiCQZQuTF;0|u#9&X|;ZsR^~OP+o^SfDZ~MM){LXLv-f#ZyZ~y*p01t2hA8-OMa05SZ1W#}UUvLI*a0h>I z2#;_HpKuDVa0|b149{>4-*67^a1Z}*5D#$?A8`^daT7mr6i;y#UvU<1{&5$7aTt$r z8J}?)uW=i{aU9Qa9p7;t?{OdhaUc(JAs=!gFLEP4awJc3C0}wTZ*nJpawv~-DW7sG zuW~EDaxBksE#Go3?{Y8yaxf2bF&}d>FLN_Lb2LwLHD7Z!Z*w<)b2yK4IiGVnuX8)U zb3D&;J>PRa?{h!@b3hMtK_7HNFLXmcbVN^dMPGDAZ*)h0bV!eMNuP8|uXIblbWG25 zP2Y4*?{rW9bWjghZ*^CHby$yeS)X-Uuk|~3V_MF2UEg(H z?{#1Qbzl#6VIOv4FLq-;c4SX>WnXq?Z+2&Yc4&`wX`gm!w`GF<)?id%1#JgmZtr$) z|8{T>cX1zgaxZstKX-IbcXeNPc5ioge|LC~cX^+8darkTzju7kcYWV?e(!gG|95~7 zc!3{yf-iW3KX`;sc!ghhhHrR>e|U(Gc!{5Qim!N!zj%z#c#Yq9j_-JnUw3U!V+{^y zY{y#*rUeR4d6i#zmT!5Ne|eaXd6}Piny-1Azj>U`d7a;Rp6_{||9PMfdZ8bBpFepF zR%3r=URtOHD!76=z*VS^da0j!qpX7~pn_UB-sELtN_B-Rpn9+md$AvTm%IW2^;qSl z)H)@5wr_j4fBS~4g1HUUHWpDr{d&01d%fR#zPHCKh*bV!{a8xH`@S!H!#{k)$Hu=$ z`%^{MwNHG=k9^6W{9TlLNp(^IJygoye9rHD&o9MU0MreBuVFxh&p&ecG>m+kZpn<)(7kc+nUwf`53@A-#U^4}bB`dk*!`BNR|&7=QI&fA)|122DcxY=8Nm zfBHw%|Ez!f&wu^jfBx@(|Nno0_$P26!GZ=4B21`oA;X3aA3}^MaU#Wv7B6DVsBt65 zjvhaP3@LIX$&w~dqD-lBCCipBU&4$jb0*E2HvVto%&BuH&z?Si0u3s3DAA%uk0MQ~ zbScxOPM<=JDs?K=s#dRJ&8l@P*REc_f(ejDg&#rwt_wL@mgAXr$Jo)nG&!bPj{S15d2HT%eFMmG$`u6YR&#zx7eD?(5?-!te z0}@!Efd?YEUVrfo1Ym*>LKvZh6H-{A{)LGx$e=(DUf7|BAA%U7h$E7CQ-&IHcp`%; z#+TuLE|wUhj5E?$qm4I8r{ad~HF)BVEH2oik2n%pq>)D=nWU1D0q9_gJofhwkUj#r zq?K1EuOo@r;Hk3t%$q?1xQot}Au*&?5JGCF0YpMn~ysH2ivnxcqq zn(2#Ss#>b6ufiIuth4TQYLJUIiYup_u1Tw}zXBVqu)`juXrn8#sH?7-?inVr&q5om zw9`@xk+HcRo93p=@|kS4-+~+duDIiddhMFY+RAFGj*{E1yYIpque|fpTd%$M;+wC& z`|{hbzyAUpu)qToT(H3hBb>0p3p3oX!w*9ovBVQoT(QL$W1O+Z8*|*T#~*_nvdANo zTykZaP#}TI2Q<`=$`UXzgUK_~T(iyhy8LnkEkuO!%PcdLGt4q8B(wxT8zgiBIen5( z1rxjsfde7HaI@7{W1V$5nO&vRL zGf)FWTzeh#*hD8}G~5NLU6Vghzuh(26j3sc1r4}Rx8##kUO8xG@bh*AE9?z+-(~*| zy4xru{qoW@`I9)_fJgq6y2=qmZn^BU(_Xt_oPZrc+bd5AAKwS`E_BR53NFFU{VCnM z^2@Wh4&twqFhhH!jDybt(lg(l>&Cl~z4zaPAHMkGlV861=cAv#`s=gbzWeXPAHV$b z(_g>+_v4?x{`>RazyJROFn|IaAOQ<#zyl&MfeKt80~_eT2kx&dcT?E{w$}g|Ebn*0 z`y25Rw*WZ6CwczMgy)8)zU{#df+}1g3tQ;I7s4=xGMpg|YiPq8;xLCg+#wHp=))fZ zF^EDOVz0PzhDu~kWhhVw;v$fO6Hy``xl;h{e%7BdpyO#OV?YaphdmVu%>mCtK?qH^ zAS`N80uLww2>#jQyw^REJsOl_?z(rP399UZ%PRvp^pVB~DBz2HbKBPSl7EvOtNR2c-GXG!)@B8hH$-tbtYG@(JRJtAyRCwAwy z2eg2WFC!y+nCQqFbQ5t40OyH}#?9UhvTKDrLppV~f=E8EYY1qg1!KS-%yo^M_*5c$ z@c6a_&@-R#@uu;znahJBG@%MzC_@|S(1$`aq7t1bMJsAiU1~*pV*@8NF$kY3s;-o0 zC`0j9{?|d3B>$=@|uA5sMn<|=RS6e zUo58?&mc@IP7{xMOmO#lN=9!s0BrB&8h%go-Pgv~ZOw#F7N|E5YYzFd?Ln=Ffr-a) z;xhsoc%2GhYrLcOhZLX)hZO(Ix2U&4nyaXaum*oEHw&%Mc z#V>h%76E&p7r--S&pFcgwsUT8wL(L1n_KPbSHn8ivYs`qYi;WRQf0Xz?bh0lz%vcA0%n^8WItx1Kk>>uv9Q<2&E_-uGB@onL0lo6irvb}5BzU+fy&rtMLO zP|J65_G5h-Z{^E?(?4mJ?KIo`uwKiJ8vg0j*_f$dsJ#k6#myV;~j7Dzo~9P z5T7W%5vT{$9?9b*>O4bvJ(`$PY(l0Q6wH@4?UHQ{Bs?M@bJ>Sl~wo|NZjZJNgD&a z+ouCwKBaccRJQC11(KzQRqPp7#rYLwvR&KX6M?k!`X#97ZO@({zk9Gv2U`PnH)_Nq zKl#dEKJ%OJ{O99P@n&D=(T57ukFTr;q=lN~kM?f{U{KSS-!JDEF76&o(qmOMe3;NRC~99zYxb+7`V4Bu&C6{^-MQ)n3;@QaT+|kX;XJ zmC&B$OavfK*g;afUkR39)%DmJbX!P0 z*dW5smLWf-0PTtCrOo0 zZCll?6DGW&+vr?gDHxym6niyTOfi7lwFmb39u_*{BSK;%O5!9^B0?!%P0ine@Qo;y zgC^zC3ZTpw!Uv@x&KkkN9{O1ty3zB*;O{xj^E?3l)hx)IF_JP--|}FRClXUEvVabj z+UpMaL`vjD9+W|Dpg+A16jJ0ya%4w(SRy)A` zTC!zZ;v-tRWn9YTT+(G-Mj>3CU=n6wS{Ps&W@0MlVlrl9 zR+P~>W@Jj{WKw2j(vrVfW@c*UW^!g{x)5f1W@w7$Xp&}W!q1zTW@@VDYO-c)c35k| zW^BslY%->D(dKR9W^U?cTB_A<`sQx}XK)(jRtRTt8s~8$Cr9p5ax!OgI_GnqBUnJ^ zbW&$^TBiq=#dT`uc5-KT_MGj2XLyR|c#>y%n&)|5s6Dhrk`gH#5X6&G>6UV7 zmlDd5X26f8K$iL_lzQozqG_6D$&>2C1uQ_-2!xgj#3YP^2K?v+C;$sMX_-<&Jp5<| z=xLw&CjJZUHR=mB8qKgj7nu*02dD%x0Tnc{<{UO)?c zs-_0%KU6|Kfa(PpK&3uv{;Rs`t6GPauEURVPNx#-K(NCI#DG9}f{zB0t>S46Py#$? zDhphJIr!)T?CL+D>IJ008x*6M!s@hAYqcVWmR7>2DyyxU=@k@2rZT__*lM^61Sa?> zkPbw%@{FXS>zuYJKv`?N%Imz|hMe}Qs&Xqq(CIvUK$!k0&phcH3`Coft3ap&t^UKo zhAKgzDg(eNy+UlnN^EIdYNlQQD3<9!q=Tkn00#VPz7FgKIIPD0DB4(Ts*3ExPVCCE zY|B!{rDiLh=0e8)Lnj7n9AN6lUg|#(?89#C1(@uQVn7BEZP8}Hvbt>2D(%u1#?B(^ z1(d+2LMgb4t3X)(EWpyNK=drh0&UJNEYpH**ov)R?5x*%ER+&#)n3A=V(qPl?8pkl z&!UaZp1|1J?cL(-Ta>N2-s#l-=n2F@6y0gtnkmnwEZ3sVB(&)Tm;ev_Lm7ZVJv3?F zQf}p1ZdH`+KbS+`3Iw~}=>pt?&StIQ!tFoEtw3bLxq>RsTyE>S?&}^!=9WRTn&~Hu zD%D&q$ZGB3#%;+CM4x)>1z%b@AE=$^h)paQg8KI@AYDD z_G<6;a&Px~@Aral_=@lNl5hE%@A;x{`l|2xvTyq$jJb4+j;xBGxNrU1ucN{*npDZI z+;9K-@0tGQFS2-xvixrW8}N|^F#n#*wj6K+J1~uMNE%4+1XFMYTkr*Aa0YAe26J!+ zd+-N?a0rX=2$OILoA3#va0;vN3bSwvyYLIca16`v4AXE8+wcwJa1QJ64)bsi`|u9~ zaS#jf5EF3`8}Sh%aS|)>5;JiVJMj}kaTH7O6jLz>hXs60Frz5Tr95yKdok8z@p4M= zd&IB0tc$aJaT=>J$&4{^rU4ny@1e*q0k3f#+wsPbK^knu8>2C<>@WY`aUctF#pH2T zNH7EY@f-v4ATx3!GfW#-MHKhL9bv6ig58M!*VRwizIilRHT6&=&`2&aw}W1Ec@~=Ps=UWa#Yko7Kk#8Y_S~o zGB7i9GXqN&)WK9pvN0cXy7-BYM@2Z7^E$J$ zyPWekH-$R4^E}h@w7jz|*MT_K^FH%4u-tPx=W{;`^gzGLKSPB)6Ldl=^rRT{Qy}z0 zLv%y~N<)KlKuh#RW3-!4bUa&hMtk%}TggT<#Y2O1Nt?8di1bpB^hvXHONU5GE5%B? z^i0#VgurxC$aGEX^iB`RO(R84duLD!^-vQvn<0cx9|cJl^-?o+Q#-YU6$DZn{slrm zbyZvSRbzF(34~M^g*<2VSA%s}i*-}}16LD;I*)Z)tMyv5wJDV~Iv;dCxAk1pbzR%F z3z_v$g!3xjbzl4SUjugjq(NQ_ML2(QU?X;7EB0c4Pd>x7Ll<)vF!p3qc4aem7I^bi z6tfjrc4vF`XS1~xKr>a`@)bxnXrp#&t9DUm!4=@L(M_{xul8)yc5OSS9f&q3(=Su7 zM`OSfeX_Hlc2AM-T>C^LKyy_kRO; zfD8D56L^6e_<Q4$ zlX!`n_=%%04MjLW!wyLWk8foY#NdMibLNU|NYwiU3qA_)1A6M2yv z`H>@ek}LU=GkKFc`IAF=luP-PQ+btJ`ITdNmTUQzb9tA0`Im!vn2Y(ClX;n&`I)16 znydMmvw54l`J2OeoXh!~(|MiS`JLl=p6mIZ^Ld~9`JV%NpbPq-6MB;axoO)$j_Y_* zEQkcxfsbc_A4vM7Q+lOa`lVxfrfd49b9$$H`lo|>sEhijlY0KCoBFAvdaA4Xs%d$KG0vNLA}5d$^1HxRZOioBO$=d%COpy0d$`yZgJtd%Vm0 zywiKV+xxxad%o-YzVmy(`}@BGe83C*z!QAI8~ni|e8MaI!ZUosJN&~#e8fxq#8Z65 zTl~die8y}1#&dkfd;G_Pe7k4+GH*MJRPo89e9Ei*%CmgSyZp<;e9X)I%+q|$+x*Sr ze9r6q&hvcF`~1%Xeb5u}A3ue78~xEEy{nS%^P)2>#V|~_Z z{YhT^)_eWegS|j@eb|$I*_(ZIM#b5y{o1oVJ&t|b!+qS#eFnb$+}r)#<9*=OectnZ z-~0W&?fu^i{@@e7QKx<3BYxs5zO4oR;yeE1Lw;{He&kbr$`psz2@u7{_N9!4B2Mw<9_bzz5(^-?)(1l13&!` zXYdn$@f-j6Ea&kn|MD|GDffQ!Lx1$=es)TK^;^H`^K|xW|Mqi#_j~{MgMavo|M-)C z`J4awqksCV|N66k`@8@9!+-qC|NPT`{oDWj{^NiC>;L}qfB*ac{{zH7fddH^G`4ebRp+kulHF^|jQl(3oHg)CDIM-7jIs@d-?YD`xkIv!Gj4GHhdUyV#SLYH+K9Oa%9Pq zDOa|98FOaMn>ly({26p;(W6P1Hhmg(YSpV*w|4y+c5KOf4_8i;RT5sM-ChTT4)g^p5yr~ef$x~AZu!I z#uqh=vB(QP6zE1FoqQ6?D5ac|$|~t2a-jaoJ5NS|%E+fU7C^+&$}-J76U{W$T$9bc zuoOs37z3mw6nu&^;Egfc+>_5f{rnTqKm~Q_&41!NGQA5-fPY;)KdOUoh`g2liYGcO5iE3MkYT!mDXBqy%pD7b(QNp1sLV9 zxEAE}63h%`4RP0Gm0gzEW}SUDrb(bP_Mh++m|<9fG68M@1s#PJ+;GJmm)vrb?eBsS zz!Arue*OLT$1+|`G`M`@9q7yA-nEfnY5{&2;)o@l znBwX@xd(&dFc9H|f({;N+u{zKHqr7bJ{jedRbH9pmR)`s=9p!kndX{pz8UA7b>5lh zo_+oq=%9rjn&_g9J{swym0p_Zrk#Eo>Zqljn(C^pz8dSSwceWRuD$*m?6Aci8`m?; zHv6Kp(I%Viw%vXkZvL6no_lSU=1yDfxb@zf@4o$hi0;7c&KvNv2fF+3#1&th@vj3H z{3pU2pPcf_EpJ+Kf#{Ch@XI~_9Q4p@mOPEpO+OuV)Bj1G_10Z~9roB|pPlyFZNDA& z+;!ib_uhT~9r)mdAD;N)jXxgwk6ygwxSVSWp5s67u;u4wIL?=EGicyr}6scH6 zD_#+cSyUk$(!iB7q`?PT6yq4lSVl9R5shh7;~Lr6MmN3@j&YRZ9O+m`JJ!z!UDOH~ z@7PB_{t=LY6yzWYSx7@35|N2i=*OI{L_nbhPaIoU~1eiD?S zbRie7k_J175|yb`{!Tma&xOENO{JJLr)rX{bUidD%-}{t}qM6y`9ASxjRd zlaN)wrBu>ThcF@&n$eWzG^trlYhDwZ+0>>deNcyGLdBHZ6z4d}Sx$4F6P@W)=Q`Pm zzgtrNr4CtW=RE0IPkY`IpZV11KKY5nEa;G#@$Bb730hEt9u%PoRp>&c`Oj}QrI~(U z=tL=6QHx#_qZ!rcMw#dbZw6GN92MzENm^2qo)o1iov242%9Mzz6s9qi=}c)_Q=8s& zlPm=(0ofse1Q0MC6Zk1kiCR>n9u=ucRq9d&sMDytbbvh_ssMvJRi<7Qt6A0RR=L{M zc{&xGbqJ{drRr6)o)xWWRqI;WT1&7#1*&BAX54*hYH>Zq(FY;5Uh0M z*HiT3fU}NOEPQeB3w}tTyxSGm){x&X}z8N}e#@P&`7g8B8>#{NK-z?MI3 zRVAMp&1qKin%SH|CL`sg{g|x?z9HVSR{6@!t?-t2>}C0Z`NvrvGyR6#Wj%?Vzx4g^Z?$Y|KjQ$0NJoTeI6kc`0HE zyMhQuzyCI8d6Ig(9FF<}sJ~%xONxA~}g+n+ut7d zx!3*fdEa~A{~q|k7yj^xUwq>qANk2w{_>gMeCIzO`q7vE{`9F|ed}Kz``Oq2_PI}b z%s<`=SHQXC<-WFN+93;9u!7|k(SG;8AO7)||NQA+fBWAb|M}Pd{`ud3|NkEV15f}5 zkN^wN01prW6HoyckO3Re0UrkPrLN5C8rU5S1_svVaCl=WyZ<2N6*R>wpm( z(GedJ5+hL(Cy^2>(Go8a6Ejg0H<1%N(Gx!r6hl!IN0Agu(G*V+6;n|aSCJK4(G_12 z7GqHsXOR|b(H3tJ7jsb;caax+(HDOa7=uw5hmjbI(HM^r8Iw^NmysEp(HS?<5DDT2 zn_?OTLVd0g8?#Xxw~-sW(Hp-J9K%r@$B`V%(Hzea9n(=A*O49D(H-9r9^)~45TY8F zq8>#DAM;Tk_t9DIF)93!9|KY#2a+Ho1t5>&APZ6<7m^_x5=s)XC?L`yCz2v7(jp^7 zB8MU)FOnlW(j!08IyN#WL=q%R(j-q3CH_l;BsnMWQqm<~5+-AkGgi_kXi_F`5+`#~ zC*i^-d7>wG5-5XGD2K8teo`llk|>i>DVLHdj}j57@hO{9DyNbvtCA*=5+|_IDz}m= zyV5HSLMv?|EWeU0%hD``axAYhD$kNF+tMwWGA(B!F5i+a>(VX}4HeZuAYtuFt_IggV$o6|WdWH~n?{yLwNI;+z<;bS^8B0H~>JG;|6bz?j6rX|0VJj>HO zMKnv7B*=H@4 zVn7d+K^xRTCoe(&F+v}dLMzll^ zMO)NG6=FpXB1T`7Mr+hWXS5)2)JAudM-8-Ybo3C%vqy)NNQ+b?2nR?tXF`pXNt@J3 zVH8Q1)F`r3U<&3Us`Lc%`cOFa{DY*0!g5^XePIWQmtNB~<};yjRrBG!~lx)e_1 z)G)skOobFG!;VZ7MFD93fF%;91hkYPDnTAJUySdEnhI^{?ZfD+;XI51#QSXEuuwIW^hRm(3{yQCnBWmhr5PBuj#q!m3v zq!Nk+J&vP4L|{2ofLo!JTkjP;ge6-OwqWzsU(v$=euYaA7FO8RVlNgiGv`Oclqd}6 zW0FKTq$N!LVG{m|MNHHIR^lL9!9fOuwOAA&2I628;-OH{1p#;$7zE)H?sZ`_MFC(y5X_-gV*q2T)@nb8PNURLnL=$oHcKk@9290C zFabCY;2%2V0))jk$`()!r8usIXmvJP@6}|=U|J5OT2M7^rxjW|fo;bYIN)|oJ4PVT zR!puIaT7OjvNmrrClSrYYvH6~JEX&S6*``^Kb}@x&o)>Nf<_3UVYxL}-Sz@@b!c;z zZ~p;hOSd4=1Tn|NYXP0){hH*Q`aqsjDB3E_AmSps_1`0vlQ9f37RoIE2*k>G9V{^Cs zxI|LLp;(hQiH(C%vfvZw!8y=@c!l*Gfa62-l?0CA4Q94xlb48hwm2A|1>Qh{Ys6a8 z{Wm(kVj{0ITV*#l4A3Q-h{xyI> zS8lmAi!D`yeVAgm)qeFAU$doo1-Ot?S(R@Fa*A@1Hzr&UA`~!2V?-d9J)x5`zyzRm zAj(xaFkp`}`GVnC0eqoAE&yEt)sK&2YZ*rZxrSGnnyYzb9%pMG6`Qx2o4eVY z=cZu4S)9k2oXgpq&l#Q5S)JFJo!i-+-x;3cS)S*ap6l73?-`%-S)ccrpZnRL{~4eI zTA&A-pbOfd4;rBpTA>%3p&Qzv9~z<~TB0YKqAS{>FB+k@BBH|?qdVH8KN_U|Lt3Oq znxsqGq)!^9Q(C20nx$LXrC%DRV_K$Xnx<>orf0es;?TWvs$aSnyb6otG^no!&8)tHRkH~{^2b{q9WaWM$q6%EW7u-w^+$R#8!6RJ4oy5U= z0>UR8!!tZbDx4=ST*E&c#6^U|cf!L%+{90uK}K9BN*u*o+{NKT#dCtiU!2Bk96Mqh zCuZEncbvy@1IKSd$9r7Jhuk%O+$MsY$dg>jNyEr%0?C&g%A;H}nw%z{T*|K;%OQix zXTr*}+{?e*Ft%JKx*W{Q+|2nx%wvMg&z#NMTrScaCf3}|=l-0|(E`q2LeA@4&-dIb z?%XBvoX-PY(5b@DTLRDr9nlkADGHq>4qee79nyh<(O1IJBi+(3-6keoB`Q54sF)-? zT{~SE(?>mpH9aLtJdq~?)Q@y7R~;j~nbc$5u}vK%P(9UEl>A&h)VnvEJ3ZI)64qs1 z*b|%9Pr}1nSwMf?d*w4Ob=@O^eb}R&u#4R!j(v0VQ`vi6I-5N;on6|$y_%`LB&_|~ zUl@VAxlWNi-L>{{%)N25{oMrzo7268%blAULUN?|n{%|>!QJ22RoqG9!sk6m&;8!- z-QDj7-P=9i8Q$UL9e1}m;rm@iCGSi3dg7P;-#5NV{sVp_2HtQkKHJT`-rIfMHwWEG zUgae|-q&V|UH;$^9@i1s;zNGnTVC5aUgw|G<3$3(6~5tV9_EF9=mRh4!%pORz2Miq z;!9rTNj~Y(ChA3W*?sB5{^eT|=y@IMmEP*>k>JOk-kpBx zp_J&6KH}m1=(pbPxwPv+;_J=+>D!*|-TmYxKJ43`-l-Vj*{2JgZ=OSp6SQg>7U;5!Lsa)e&iEh?rVNRN#E~pkn<;>^@p_bJ>v3HKJ%-7 z^VOdA>)!B9U-XHd@Y5dcXWmX*`GB*&^^4#BGhe?WV*lpnzUVu@_SaJLaX)w2efJH% z^!fhnfgkjvp6nyv_`iQgk)IDV@@Y|o|7hn7No%5CcxE9(ga`{J6e#iG#D*6iYK*AyqQi|C8CFy% zvE)aLCPj_}84{$)moQ_>oJq5$&6_xL>fFh*r_Y~2g9;r=w5ZXeNRujE%CxD|r%ZeYcwK>z&@e?s6M3yTF)>WDDF2%fg zDem3acd%cSE>9vR8MAIlzJ(nphD-jrvgON|Gi%<=xwGfbphJruO}ez{)2LIcUd_67 z%(ibs%k|?rv}2F8bL-yCySMM(z=I1PPQ1AB$*>lT7?f96n`uFhT%b!obzWw|7^XuQw|MGdz(DmmNd+q%t;DHDx zsNjMOHt67k5Jo5=g#UdO;Dj$(l%abVcIe@UAciR7h$NP1;)w@V=-GuRw&>!EFvck3 zj5OA0+*pMVA`sGu4Y zDiNNT={e}4j5g}%qmV`_X@iAUS`ea_DLUz#YBjD$bp$qDLD{EcNQ^ufPT??6AZZYwWSeCadhS%r@)nv(QE>?X=WZYwfkz zW~=SC+;;2jx8Q~=?zrTZYwo$|rmOC{?6&LfyYR*<@4WQZYj3-@nuMukJ#t9vzW@g; z@W2EY9HXr$p&A>2t0rXd!w^R-@x&BY++Ds7E3E3S23qX##~_C+^2j8Qg|R~#>zZ-N zEVu0P%P_~>^2xP2{=9Exvvn-<&OG<*^UpvtO0%T`-AvhR3kR+A(o8q)^wUEQU8$XE z7!8-EP-m_6)?9b(wR}=*VCtc{GMq&gU#G41+HAM&_HJQ|b8A6omknASSy=J*-hB7% z_uqhL75Cf)S&>B@q@`iU6=(?z{(T=u5xTtqA66+|C*7c+2ibY}1|8`}5F8 zFa7joRz3XT#k=0wK%L881@X<_M*aBYmv8?04jSJW=ZjZyee3FLMo{-wC{o4!ROGM! z{`~jv|Nj8~44?o9NWcOb@PG(RpaK`jzy><-fe?(K1Sd$r3R>`j7|fssH^{*bdhmlF z450`|NWv1D@PsH#p$b>X!WO#lg)oev3};Bg8rtxNILx6AcR0ZNxz7rWOO?MiqY$~+ z;c`}}pCXpX#3nlNiBOE96sJhVDq8W1Sj?gpx5&jVdhv^345JvwNX9an@r-CpqZ-%9 z#x}a~jc|;k9Op>KI@ z2TIU_8uXwDO{hW_%Fu>7^q~-qs6;19(TZC1q8QDnMmNgQj(YT?APuQVM@rI?n)IY7 zO{q%pInDx=Q+=0=sZ3`|Q=0yxrZv4OPIt=Fp8E8sKn+)v8+cs#wjcR=3L4u6p&WU=6ES$4b_+n)R$`O{-eh%GS2J^{sG?tNvW) zO4qvDRdeLDjrtU#5{}%)uOtDi>%@n?!W#Clh)wL~45K|>#V<2M3@l$Kdpp6}t|5P= zq+&PA+0J_Qvr-WWVIA98BJQDjje2FOWT?d<|dlOOk`1eTf$Oy zuzK-TZHG(T;u`m`8p7>kb$eRiYRI>L$*f> z?3(wy=uNNIN=uXSQf9Z>W6yNN3z+7q_rCbeuYR}YUbw)Qw37YpVp=QUyzckF2u`qq zL)PE5{CBmC=@nqctC-|2_`(>@u!ddLV6{NkyV9NScOy)o4V(DHC{FR8{zhBL<*N9_ zFpjZ|0mfp;c6i1(&asYn45%90n7up>vXF;NWHa5^!$&T%l9$ZnCez5rKX$Q`r%dH4 zTe(B_eI1mq%;he7`OEUjvX;dR<}#c4%xF$diAVF=G`soDaE`NS+Dw{z+4;_R&aQbBf)TpNOX;RJVR=fJuu#UB?XHDx`+xphH&b6+0&Fenrrq{p@wy=jy>|z`H z*vL+{vX{;5W;GQ#a1%Qr%qWnDQRJr;Il%fxy zt6(Wt2fEnDE_A4y{p@JpOghA_066eu67SH!)+KWFvM+@kA5cIIBvK9wV8HG_fxFyG zvi1cqT`6H7JK59D_{KY)EM@3=2d2&fo@ip~b1z8Uo6`Puy(i`J7sxzRB0-DNcVg-Y zD1GVU@c7od{`FKj{RLJq^{Vd#^rGK9Q#!wQq~QK4php1ghfnHu6W_E{Q_T?h$!rV z0idJ*4gZ+LI5fca%Ku~fV^@7Z2Y8=w5A_FrCeVBTPzm^O0ea_sKvx3lHxt1p5lMFe z2VfG*#{e-9d~o-DBOn7N5efzfbRn>P#+QOBxO11#fWl`EKqr2Dhk4OAfB-0V0(b%R zpm>HS5tz_`iZ^$`2ZUlbckDNI?&ozD_#RA=yK2Zb!UhG_~(2yNOuER z5WnY#KxYCx5DGw;0tGP%>{oe{z=f4JecC4o7s!DU!Fv+%e)0Ew@K*zp;0Yt(gd=cx zMCX7r0Em64d4Kp1FqnKS00*N04?vdyZl{G8zyK^D2&0H~6o`5yVTJyuia_^>-)D<( z017~800kj?3Lt`+Fm(!0igg%`(s*(kcz7F#g8%S*xtEH8h>94fezGTg|8NdKhlpl4 zbr}eH4DbSi_=}cU5SaK6k(Y`laRnyv{&VYSiqI#BLZ^tyhmHT>hzkgWzo-u7NE2b0 zjJ4;7&zOd~_zz>a03x`7>{t-wID6BWks9f7dANt~XM5RrivOT{-8hmlC=p}mdHC3T z{-}4sw}<}_j|K6LF{y+J`4aj!4LVpn!0xp|v_jwW$|Ri}jw$%p$W6E``J4LOEF zh3|9t zkI(m&Ch>mucYr=AlAAY|Ch>b`IfF?FhHTlESa*R(mxfj8jV*A4LAaHU`Irkgk5?#m z{-=#KNS1nOmJR56?1upv@R^|gnE*9#lQOw}a7mb7$Coehd`riAB$=65r<&tOgMTTO z2sxPNc$Frphx(Y9Qn{Gt2b#i3mylVU#_4aeH3Q&|grkU+ewdGnsgGkRbh7827l??p8Ipks6E}&92{49V*`5^HjA(eA_IaNGXN4)C zdZjmtJs6Z~xQAnine`c-S-6>d*Ol@264wcTl}LuD36Tv5o+UAz6*-{KnSuN`kpEZ` zTzGWpX_>pIhO0Ss<4|nId1nZJZkGk0Ko9=g*Psi^ek$ORZux!;umVQViJz!-aX<-d_?$udo7?GtFc1fq z0E-u30QeW8BFUb(nTTDt0R`%iOZN|-0EF?kirR;NlrRn@K%;!xr{{KozWIhg$D4Ku zbPI3~sVJFB7@Y`;l!|wwOezswIjA-1pz=tjW2bk?X{d;*i;C)?RA_~YrvNl?pr^^4 zO1Y*5A*#Qah6^BtWjcYPiiCbztF{VfISLatih}0Ae-|JEF+hbsXn*3zsHgf5BzS-r zAOb9af00@cW$+H)mw~1E51!De7r+7jH>V~D1HuY~7*MSAXsy`F0V@ELXu6&Zs;d70 zphwx8J_>&4I{uW{2Xq-g0?>G?`ns?DY7??2dWhMt0z0q-%M(|3n6pWu1-q~e%dlBM z3S^3meaWs3Td@{PqX0;%>#DF9+p!*dpY(}-Uzo6g`mrW^vM8IfD!Z~Q+p;eEvM?L7 zGCQ+0TeCKMvpAcxI=izx+p|9Vvp^fPLOZlXTeL=dSa+4QO1rd7+q6#mv``zhQaiO& zTeVhuwOE_ATD!Ge+qGW%wO|{zVmr2GUFlY(Nb#rws4zWZmSw;Kn8Jp zw|IM8WPn$tQMY;fw}3ladz%__8@PshxQSJ`s)D$T+qi~hH;e1Ik~_K51Gx;tx0IW? znhQE|{(-qnk~y0jx}qyNisQMPfj6U@x~ls(hcgFBizzpobGqJyDaUl#*!DnC&>Wc#Y z8?3wskq#mJ01p7e8Y~bTOb_bY#JI}o5Tw9Ljkd#2Gvg zw`;@)@xKdz$gbwdwtUM$0m%}f0r+bZmuwc9ED{wgsYMKk?aRS(2$2=AzStW9#3{>r zXTn2C0S(Z6%AsnwoXy%C6S{l>xJx)R@ylfq%n38h65$UqEP~Sf59%-h4N(4u=ex8#48y7X)9NdlcdW>I-PnA6#bC*P{MmX+dfUm768K!pa9JcYrEas+U>|ylh7<}+I8@4$^zQk zUEX0l7v5bN2hllMAl}I`Vdfp*HC#66y>(R8`?@ubba!_*DBUbrbax{X zQVNPFA%b*>0g4g^DIuXqcXvpKNOyOGi{EE~TixfLz0bM#_l|em_l~>99E_#@v-i+7 z*L>!j&+{E`L7dngo7iX?&JEto>r*AQ5_;AiRe$KRN z&U}2%0ybw!J8yjpD*YZ^CTJ>=k39Asips3=E!eyZ?SiZH!aa)x_lFCf@e5v63*O@k z4`B;_w2J}Ki$NBPArBYBs+1yMqD(i~U2@k{dWkY2xb)IuDf;13Z2Z#es-?H%OYyL! zMB3$K>E-tp%V`gnGvb#stCq9Jmph0xZ}H7cNX@SBka4-Kl*F%;Re|k*l`7au4ee^3 z^lF2}YSY8jmiX1Ss@0D1)i1EsZrU}f)DlNpOKjPq;rO+Ys8Cy&Ly0kW_|);EsH)|f{!e_gtx7zw07+`5k?Vq z&z|kdRZlQ{+R?4UHbvP^|!9=YWeQFLk@y;tT{FgJTL8g zUEcTEIJgcw;|5Q2iq)zKzP$5@j>2<-AmX-_24pvk&fM!7zV~JGgpI=)n!UKQ za5r{+>2?c!Pl~iovJH-`-O19=@Lp)qXKV4q;GHyeRlLFD=6`C2Wq=G9*dIgdBQ=`Co+OZmW%DfJh%lNFxQD!kTT(oNHN zCml@b26KRh;pUh=J=C@`d}NNoEAJ_^YM5zO=_Td4$JK;2%KRGT#9+-l-#m4^@r}`j zMU~HY7z(xVre$3)4xQp#<1Oo^r!Qcsm&-VtBdDQN6k371)V3AGr{I0D(aV_jY z&7kvAXF5-{U5?4N(@3H7MAJL7gInX3K4+-3=7%oRjZd}K!YSPCLfhhOg4ZP1?+xMT z*gLI=@$Ai6bJ3yES)6)q&vhm!C0Km-+Fu*14!x{<*W-9^ZKBziRcL=8VFz-`D}4!# zIadY=PyUK53fc8sSu{Fxm#Q{ivPo6-D`LjHcmOP&V2}YEQ z92xA|J%W1dMz7DAvxW_PN;Dp`pOxr*k@=MwLd`Q3$Rp%?r77$sHN;PyW+Rn3(%y_@&7u3@bnJduKf}jZL&yc$n=wsOi zSteuE@d>GZl@As&G_g-DG&H;2XuzavHn%5@%`mChgyz{OXk-(iAZ%hAV_WnU-(yW^ zWNsg69~+ys&d)&av0IVZ?P?Se^E=HfMwcB@6+AxnXG$>*KMENus&`#TEw*^D(I|2a zcBf0Dcp`+?GZAw0^T43atvv-%t6(y_606Xh*`aHXa&9ONd#-tj+B_ARFR_V$kY1jC zq-3#Z8dg+#Xb@>+S85w`Ekw*NNuITIUZA1YkJcY2+Q=@!7ggLLDfAWv@0$qe*MiA0 zc4dy~2_YlKZINlOXX8_v#Bb&l&zG%mIDQm&;=|i7bIR{hlyENmmZj!YY^EU4Tey%` zeye<=>5W3}#QcN0k{wh@ms&Koip|&16^Sio3B9i_&2*uXuB~f|=6Bk8uAg?biY!#z z?Sh0-)Nm`HRo?5+Ye3N5|N}t235G*A1}xfnMG6<)37^$ zticjhs6-amy*rQ&*$Q1j6iOJ~9mFPYg=tU%B`xX>z8qwQZ7V8AIoKT{(qM(_Rw75c z-yI4;w#E+@m1iXF2~(1{CVW*Q&nn#WNGr&iI8{`E)37Jp2>a2MSOyVp_nybsf~F~A z8L-1^ppVUwZ72syn&`rH$aDSBIXg-ega><`c@!j3S^0;Z6ZKAVhRx%+XZ=h@d1i<67#!nHiGOW^{z)+J?x8p zYJ(v*RI2rGzb~P{DP0l;JiA*lEPk9jjcqHA&=%I0gcr;X$uZFmH|$Sd(ovQoqWlv4 zni)sl(oSAMT=&Ib9)i5ff`UPrZuGt#10~7=-__6;A<_M5B8^KNY$Lj``2GI$Cywaq z8%s11gO?D*6mIA;VET!_-%ZyFzM-FzOq#^GtL&Ituk)(R;A0vQg1d74L4&wqUi3h= zy~0h?filCQqJf;-!8b1s#14G)C{#BP!%Pvsp7io6ij(C#3P0}tOP@j&oUBR8jhloA zKSu;RU1yOnX)_$mi)qZZusu!|1W(CIKw0C)qB87=9xO;#aK2$sZaP>rSokT}*~wPo z+lC`4s7%W_xRsk394CD!K)K}-EMY!PI#klEaLe^ox%oWeP?1rN+^zc(SI3T{3pyKb zxfhmSU3VWUpFp|o*(hPrBu%DRl6uPva`WnT(NN_^@NMroiEGEO!J(=Hp`80-R9E-* zi?dc6Z~LK0S|XAS*Pto71dwoF`(B1#2@AfWd#S<_(`dMktjQ%rsKN@@!`KvOygQId za{7hla6Oyiop6H+YtrK3#>=?8VLZ&%ghS-XtRZ)vxmDQE9t<}_P+cQ;m}fA1hMSbu z*`5VgTxS*e+Nu@e`ZAUJ8lzrIi*l2z_$x_UUXQQs*HG`qHcHwR7~Y2(eQ|xAT58K* z{I&CT$lbSdlJ??ejU8uT<8NyOlby)7ubJ+JK9+0h#N^C`jSuH?q;PJS z=ewdL+9`|JeF&g1zw}L6aX){c@|Jt?x3LXe*Mb#+HM_b=Y%_$y5}OZehRbE&GOzo~ z;@9HbE}%yh6%DMiq1fbqd(yHL;?ck&eJ9*#bb15WV~fseS3PV7&u+0)-|bv8%$&rr z&y^GTQi*uGASCoM(0JE!K1Xt-#s02MX_(G6ER2Fy`9mEa0Nk#O7#-L zb3A$!egt6}hpn}%S-OMvR|4zI?%$&&Zy@HOlc^zbb=D)DgrQ9G2DPDSuLULaA_!%8 zHEWaxP>?6n-=lBR(Q(=K#jC`&j7aOaEA3fFHoh6EBst+;^)|k5dj`R>9Q?ys3d;4L zO_RJ-f}@LGweWiIiq;$!>5IMG%R#H`iQdp?3xwzS?^bU@>L-Ri-VfyVLCF7tSVRZW zsAU^Js78CZ84U40;ieUS8Z$Tk?V;PZ1l`@Yq_$W!Pj6BXHxMV^t8LASZzCm^-_G`~ z-COwLoKr>pim#@u4a;()D20yDl*VgyPinW1sFq4e2;uk}Y^UfA|MvW<)HG4eCFUAL zL7B$_RYNjW#)MK|sr*opC4ISsi?W0;xrJd!VtRy9y*n_-$b-te`NanUEIT`p4GETc zD!&9SB%e3_{4%0GSyiT72u@KFz7nbHM6%;2IY_g$w7a!*$WTKynReqEBWvlLrRtk7Zg+x`5_sb+o6sO6t za7xyaL^kjTRktA(g;2>}F>eT=mk>WK)y)tVkPM^-e+yVeyxBFoX5}J&P=rxg>7YjA zmyIUx6>xXzOA#6jt*b-2MM9|RFKIWR3CcThA(X({Ylz<$a!@O#rwHi~Dynl!IS@Aq zum{~JXo6k|PD!rj?kFohDPK>idY~u4Az!=R-X!kbXsX}rcD>pCUh_toLSajzL0(-* zhS2A}GV4(x^e1)gdMsDV1FjJ(=0(-(K_L#zwf4wT!zbkw__Dhzs9~!$vij2ID^)k& z6mA5vN_aOv+ii(JZjIE>_N)lMug`-0vf-sU_&*UJo++>H+TbseA>@*M3GEc3m#Jd@t(@e#g8yjCJ>u4^hPLefwj4>W z7X_G6>ZvAoYu>F^n@9vYA^VYbSD8L2-$g=zk$6hKkxxa;s1mRY{B(kPJJLUjseD1N zl=w<}qIYZFo5E~~OvJ=;YdMJUV)c!D$pqxWx9YWplGOtA0k!hYE7NVAvmIY{JGz{N zg2F$1oBrgOsT`VLbE!o#m>tDNAG#)hPQBS|&;|(=l^PaoSPoR2<3i6f(+G}Kgx!_# z8I}J2xby35wd_5GPUOz{dtVlUzAUcde!7RAdrvwUTa8aJl{nYGT(30E%zs@HNq#eA zKvGd(7wq&_Ko|W_BvLn>nouibTqKmz1IsK=$}pa&H#U4pK`P=|MBR~3E$XQ4c=-iM zp&QviyX*azkX%h0CJqjHwrA$un1$U~jWF#?PTC=!+UO)7*!o*)^4qXkdI*Jjh@>iT zu7-ibVZP7~vdQvbZ&pl&9?HTVs>U8wJ)L079@4e+{s&S#R6W!zy^KP=Of&^F8ez11 zJw79XAEJ6PuWK@=_Hq{Xa=qhc5khBm?u8}maHI5HA?f4OxWdDO&b!vTd#uZE+b8VS zClajGL!G(9*(s>eCluT#InXCHr#H;sCm^FIF~=jh)&~{pms99}{!%Y%KPyYW z{HuPI)P7ZxEC^1&5>AeCVZY{HzZS~C^V_P_I zNPhZm17?i_<^uzXru~(!xr;9bOkh%ZrZl}*g$8XD2CsK!Sd3*~b5^k;DZEY2W1Bj7 zqj1pdd!N7}7acl>!$3abrhi@8;2oAB*H`^cjhS5CDz^>tZAFUqiAWsehzzC;P4LW_})R33qLkjFb+|KQ=USH_FX!(ZA00>6jo z8W^8p`rURg_I)=TN%HmifNB6rf0ekgDL#wti1BzE3&J6)>EW<7%h%V1U*7~PL`i*p zT!^TU5s;7?kVrC;#4?gBG?Jn)^4?%1)pjJ!Z6rN-BqKE-AqP<*H-HPlb(6@YrZv_V5V1>5dirl^x2Y)Mh^{q73EUz#is}oTo@$1LAZ&iC{ zbtk4WYuUM-h>$Cz^$Md62BVF(qfKt3&B3EBuSQ!_N81WV>pKG~oHIkefBUjG+V!ra zMyl^iW?-+-Sf9dJzromm?bx8(*ii7;@T;+}sbeD=S7i~$MhC{mY(;vm_NpL`Pm+vJ zv5ZgSSd1%-&l-&D?~Kj4jV}a`8#<3Krj9QcTFAW{Ul|x*o3r5V9A8J7*d)0|^nH9w zXktg<+5-2)uIh%=W;1tdL)T#9#E$TEqsWs=x6oc?IlcM!%+Z40i zG^_jcR#th78L5Y;B41{8Sm$)B@QgfBp;a?-(btX1;rE0Ugy&4s=1euiIKxnF z$wM?l&^2Ml)7F-#j*+%ztn=3Hvz(ErcI>DUO*4-cnYGhGOs>p3rrC+bqufxog3E^X;`(bAiyrMH7i@8*}{_m>h-mlH{slUSFN zg_lzlm){#Mr`j#2xi6=OEN4V7e@I)-ELzUOb$k@E=;(=RYi#HKCF~Y0D!6xSABGA8 z|G77`R|@S`iriOS_(?YAx$(o$zYC;%bB8 zYNOq1lly8jIBOBT+M2f7R!u9^|kg z>x#Kw7YxC>t`|BI18=IsXTdW(~!W%n^8@q-Zdv+W9?i&Xo8;8*w zM`;_!MH?qg8>fRC-{&`A`x|Gdn+Rks^XqG}q^P`_Hs8k4HMgxj(}K~+U?J!+n;7Yv zn8lk|&70Umn>Y)bxCfhfXj}MXTLf%dgd$r+N?XK6TO{^dq#j#jpve8@K`ea-9D0yw-jN*Iky_Z1 zKG=bv?aGkt%ChZ3MRw(scIAzB73_BvJ$98sca>vyRnm7=i+9zUch!e>H5PU?4|cWC z_O!|NblCQEL+@U(M-k%1Ks{j6ciA%v-7}8aGfCewE#5P0-ZQTq+Pk{2XK}E14Q=0& zY~PA)-&$nfMrr@L(Y~$yzMaRuedxYJ%)Vp#{*B`Oo6Y-9L;KDP`?n7EZ=)T!kR9A% zJ8%^_xT|z<&j^Iw2kssR9-#-G2X2N&7>30M51J3WhYoxe4jvvH_@W*9ksbQ89R`RT z1}Yr}865`OABK1whK3%7#T-6LKMXHEeB6BaWa#ke!r`-n!w9saNV22nY)3Cdj-r%~ zUK$;}vOkLUIEo29ij6snOFw#Dd_UwM%nt#9;RyK>8p0U@cLBl>GA#l#LOKEh!lg@> z5D^iPkdTm(kx@`kP*G9Q(9qD)F)%T)v9NG)aPV<)3GwlX2na|B2}y~F$cc$5h>0ml zNT^6jsYyv`$jE2`$jRx>K~F&e2PGvOR8;iT)C@E<479Y2baYG%3@l7ctSl_-Y;2qy z99$e6+?<>|TwJ`|+?Tnz!FT)>US7V-m-((-;pgMy=jRs?6ciE`78Mm07Za0|kdT&? zgh)xrN=rkfrR5+Hc^Mf609jc@04P)m3RRYqgF{|k1wcVTMNv^zNl8^%SxrSnT~$>> zO-(~XLsLsjTSrGnS65e0PfuT8-@w4Y(9qDx$jI2(*u=!d)YR0>%*@=}{OZ-K78VxQ zu3fXVw6e0Yv9`9gv9YtcZhzg@!Pd^v&d$-^{)U6Y4aXZdZr;4<+9?1 z=jZS59}o}_7#J876cijB91;=|8X6iF7WU}Tqww(X$B!RBdGh4x)2Gj#J&TBlh>VPU z{`~oi7cZisqF%my`RdiH=;-K}n3&ku*tod3*RNl{dGqG&+qduDy^D{JPe@2eOiWBl zN=i;ne*Zo-EiFAGBQrBIJ1hHRc24fckDou~=I4Gc$o*XSIj<-$zc{a;Bp;x#w4ksI zj-v9y3lvuroujyx++Q!Dl*4Ea}&hGyH{=vb) z(b3WI`RnB55PXswhYMGd8~uqm zq$|+*Qh-N}VTjD}c~bcWvJ|f5qwR#YS5#QXJ7l^H?fwFeyxw9i<^n$g!;VRo-PSDE2XT6#d}+yYyFC}W8v0V5rac%raGHz z*jTv>+5B#-W^InYf;r2R6G5A6wmhieYTQK05F8lp+1=#TYgMfQ7=*$Znzi-UIe5>$ zHIdslFi^h`bF+3zjOsuXnUNRg4X(LcZsAZBtC#-5A-zq;zevLR_PdtXE;ocrJXdE7 zDa1R)%I9>JyK*2nl=|E)<6WN!QyStcz1x?+b+t==v=F!-dFj$GmZ(90yTtH4vMNUo? zKwcgW1qC%lMKvWQbwCvVRhEF$gtfJ`jg8Iq>(^~lrwBMp0Ez$r{7_L@TUA|OQ`=Bi-_+31+}PC8 z+}zgE+TPaI(ca$K(eb6Tv+K*3ZU8_JU0po@-QB$afG2u-`T$k*_Vxp|=<6Hk?;re0 z80U0x!4-fgCeMlDoGE5!7Zw(lmX=mmSJ&6qH#au{UF`0{=>l-Y1xo;i_*-K5(;xm~ ziQL56Ky)nEE77@0^`ZDEuH?Gh$&HV3#ZIZ}@Kc&2D8bznWkPG^=PY`{l8?YamH=+E zs(Tv#c@wB1;cq&=3Jx^vnpkW`)|?=S2WD!5c1kJKmR_8Nb==C0lc$^!97bHf~@ zQp^gnbuQ!22cv~M!pN(J0W0XZacUkP&4Q7J2x^47!?Mb`P?m*AZ6-_V{0MGPg9PKV z?*ZkRMnxfw|Fb4@dDsFDpKU3fqA;m&ck65yMtdoK#;q9ae%v^92?JDB!C{<7VpH$2 z|HJA*Md?n!fiTm7l2mE6??A-0=Bi51gVly|%ew~_peeY8*m;vZ{YTO;#2TDD)#0z zxH183^DmXjY-_VTlGsoN4d$hmSodD-7bf>5# zX#T^xlONc3s;W5JSyVdNQTiT#va~v+W`Cw7eeZa0OJwM_uWv1^eTfa)@P>_seIIa0dIH65O}VQ?ONReT2T@%bLi&L45g{EF6$cFs7Yz*$ z9UUJ7gAfyw2mlL<7z>L88ykQW2M2%*7Z-pW4-bF>A0L2{fPjjS5P+JP7)YdFj0FS{ z1;r0gQZi6c0WeZiGt$ri1;hjt5IsFJ7@HUwS(%yH&L<}hFgbB?ar5%>UIBv=d`J=o zL(+x(5f_)1kbp=^UML{B3lRh;>JJ?R7eTVJKSU4|stWK=br6t3%E}t5s+wwQTI%Xr znwr|$+F)ja4^01RTKZp3`kj(~a8gxO6`-WrTEI#5f951WNEe*c1~{n|u8DrkOJBZp z0r>-X2uw#{E&@Xln23PPnVg)SnwpuOo}HbYo1LAXn_B<`1mw=b!ZM(t#l;nXr6qt> z0KiH>0|8=MU*Fo?+y+zAIX&&|?SaYZ@bK`*?DUhO{$3XSBPVg1x18%Bx?3`&pMee{ z7q~Gh>ViZNPAwNst&%qz;!CHNY2KP=TN}kLTLyM8S>+axp6H z^Ttv^Xl%l(Qt~k8!lfaavs4(G?N^9TflB?u%32!4$QI*Cm`LZ|dat~A%X=Irdg8I> zh}$*{E;PI|Jsu+Wi!wW2+J8-f5k07DH96c_?8~~=RaJTN4?CFE)nEe-m3`6=nbhGQ zb}*-chz%X4g30YNrb4Lyyo1S}mN6a9{SP~s^BFVGB=`TkgUR|~_Jz9epLQ^f?LN#! zoBy_h={O(fkoEzrV1Aj6fQVONusw6BQc0I_9}!)E-0v-6OYm(XcL2}wkY9Z4KOM*Z z_%WjCqO5D(FneOuYh%avJcg={Y1meo$!XyM$4MEk;Uf$gsju6n60+0093HVe@H$>j zPio4%5#~1XAuEM9nldvvjBsQjyYkD@(vy$N_qiYcuI2JBks#4u8asEHfYJ2tynq=6 zUciq6uiq^xbaZq~U`SzOGcYhPGBPqTF)=eU zv#_wRva+(Vv9YtW1HR88j5>1XQqS7+N|3oX8M++UvmlK3c-x>BTIDUDO?qhx1U;mT01O?$jXdC}Upl7GIs z{#RNK_J@}H@6Ve*SJ&?iIxf8Pod57^b$u~0i1fx{61ez}7Zc8#IhPzR&8hv~So|0- z)zH-ILzs7__9-Wf_0g5a`!{5TLp2;$Jb?k z3DO3{WW-^FC%@VEz`6&%{RK&4Vd3H65a8kx;o}n%5ReiQk`WQXXJ%qzDiRW4(!pK( zbCV7j_rRosgPffHC(a#w2B1d&vx4+LL#!9PDk~!cm6er)LgnS;6aeJq6)&jue~I-U z?EAkNlEHWk24gT3gNYc7!=&wVcJLn z;c!9P@;RmX8D)_9sdC=?)+lzB8;5NC=foOs-s<|k1AY{QSvem(-AyL&;p&ne{3r+s zArD-TCc*`&>YlCs2ZQ_@bDktCgP$yE+zW^J!BG$rq9(4#MQdL_B2ImFQ#0Z+_g|PF z99!A_SmWw-gh{$=G+9y;NVLFxs+Azx5YA*~o1sN3=>lcG8Lc8nBg>E|7dWmyV%v2O z%@T)sIP|A@)oM1SMUBh3i97bkP;lt%)ddb#8`-S z9W+Ugy_Fod5clBqSNk~M7Zi)JfzwM7@uAys83~UOM=}y42q{0r9$!gkPmZ<7k(3ZC5!?~dFI_@LL_$SIMngftKtaVsMZ-cv!$w2LK}W~Mz`(=6 z#KXiQz``QL#wNnSA;!fe!NViP$0s8oASWdJL5m>CBqO6CCkLRVpn!vtk`CZJz@$G9 zFd69R7#SFt7#ZRE^1q}mfwTn5639wvDJh7wG(5?KK;&d(;7O*etO67Yk1^%sl;q_B zlob?|6%|#^158yFl^^=@f7Ru`?-T-$5Ez8#bO>BQpd)LltN$Rxx`w*CMnH&vaAGsy z#GkbI2Pd}uWI!;OUvS?a#0O6;&WR5mT!6d+jOyo!CfqaxStf`kK#U18Ob}q6OUvnt z3=_yopecc(Tv=K9=@;(o>|Dsnp9A^7BPxF;6n{;E9g*rkrt+38s}Kef3HVh0UL}+Q z=6|6sX;D4~fy+o5FF0&ND&Uuqg2mim)T;gV+DQsgoVNFgmwMB+1%%xw3o{K8(d{k9 z+6*T3{dWr7X|VRPYCNo4Gg)pJCYBf6oEdlG!lyAW^}O0k;;?>eyjqm%*$!4oZc`=V zjz5!A$sPHIqsh`4;fBw)ACQL#sOw4v7TOcpaLt?j24xuYpjlP1QGx=bNR71?8YfmL zzAJLX(_1Jv-#yPGJ3Q0S7+>BVKF|0^U|`rciW_We7E4yioLO9M>#If$`gDND>X zaSFQ_jSvjGmv-?cX)hgVzoxxbtrDAy!sv{fePjB5W@7_WHW;zNa191)L4JN=K|xVrVQ~== zNl{TLad8luKqV!CBMA}{B?tr_mjEO3XIAoiN+Ktx{uA=@8UP9kngEK509pV_N?OXw z+5jplIsmGwx@u~=>gsv`8X5rlnwoHEX&Gp18|dg5>gpQl=^5$k8ygy$7#W$Gn3$WL zrzoJ@VP$3g|8!jYH>&VoMkgRJ0jvlHYv4$Nh~&Jm0k8c{{YbA7#OCSM(bvZgZ4ul}!!?E$n@d<#bpFZZy z%p53pfXsGwZeeb2ac+JQ7@DB#F%LXVU}&COnaj(ofG|Pz<2=p<+7Yxr0B8OXk3cj2 z&X|8qi12Z{7Q2%BLKT8>TeG8^DWxTfT@P+kT7~$sn4O=B(+Ik3_4=F;6A14vBZF~U zCjlBt3m>;z-h)GN?^N9w`ZItkbn`k_g%8~KD2uX9UfG;9O12lR^#vUdmc3IG$*3!l zSXmx>;Ib;?<$8oUzT!b&0aRfzk7PHsbz@{LKk1^@=uAuO2hIIrt%M@lI5~Tb8xfWf zeHmJhEqDwvDp871QJ*)K71{ZJ@<}3BzE!+7p?+;1hBh|@@ojKKx~3^8SB1j*;GGOi z>&@-?;rsjDk!0Z?{k2~%+tY0jjbZrj-AbkJs!D%hmDOfRFj)1l^QxBUrTngchY-mV zA^ncV^r19f?DVeG;iB|zEU(cmLpd^suZ8iH8D9%)?qQ$t6mIggaTgveVY)AMe8}W3 z8z9PjS9UOV_IWIiBXd+C&j;q0*(zczuaZpevqV34#7K@dFS3Q-i@Q(y1o`>V`?$c* z!^zK_I76miyEKW#3VKc-X~lbwP?(7Lxie`e1|4IVh=mabEhayrq@ZW|jbD=-~_Mn&aUNTZ>l zt)&G_M|iIdwA%i*()K^WYy{K?65~JO;;bxqR{Sw1=Tpu_vF#JQwUP_!ZMpfMKNkSx ziynAK~%2(+F~!KfK02 zBAU*>tF-;>vyJ>{wEe2IT?EHqAU+R{;X-hIedD~P0_U~eb7uRU+>U?H+xa;BQ}_MF zo4;qj#>d1$^!4zW_`+=LO+jU#IuB{!Gcl;Wh4Nn<$}A9zvTJ*Hekk*$rB(uvgIC*j zXLR0czz=2aP3XbdZ;Wz>)9@oWl-allzf%d!MsD!Tp>M@zU%zBw6o0(>!Rf3vR4;XHXh3Y#DC#KP83{S6bKX}U;h*>38h0kr zEh}6|Uq;7_(|Ay{^6gbbrbO<|#OEd@fp^zSB}^XsPd&rBw(e=S<*WVjdpjD9=+26- zP>Dn&;aAM%v!ZVt&6-so@$n!@cvR?(UPlKWjXC&X&tk9+57+n~xo2XNI2@rYlJaS} z^gi)BKa>gXC*lR--KsOd(np6yg)t>NOo#J)P-c95 zCHlQ}INzXX?-Rk13>ziU`I4Ck;yqD@NO{G6n{Xx453@2VxTUjEs4S0|U+QBrgU9Mf z1NJi^Eu9N8b2piO1;3UV%ARI2|JZ(nJvmCcXesrTOCHBU1PF)h-+HX0v%aHw{dFP4 z<=9~%QO3)dJ=r3E#38)DOX#fa`XbFkv2#M*4i0Oz)=t)Q!NXY;g$;g?=DVQh# zD48j#n5h7$S*U4PXlPk!>DcJ#+36WL7#TT1Lx!1|gO!y72tN?8fOv)HPuYq9P=Epg zq5^_qfVF)Q!c!q3rcqyO3GTwK*g!*s;cR$sTruL8>(v<^f0X_;$j{|VaK=GrQ;0+Id{N(YIr%#?fd-^Qm*|W%qi02WJFCw2uJ%17PBI+fm@c!1| z1@+xGpuPL%?Yp;cuVFt-zc7|3~co9!mf0$y^ZKAJNLco2`IJ7g&6twR=u+ z7wq=4uKQ1dl=Fxhcz;VEp9Yifxt0Ufyo+S|XF=~=)WKEVk52DTMfVpx_v5$!g6=Ss zw#ZR_WYX%3D%b`5QNr-)m$`wu7v>yg1TJ5dH0%Lu+nqhcbwpMZ;*0??%3TmBP-s`7Za0cQ5DH`ymzp}9{hP$+j2WJL))g=lj*UY z=+{gccgFD}J&$RD#hzS}=&F^NcNjSo4})nBMT>5y>u{YQ-Ra*q9YT3_>V+&iwR1XIA)s{SY_Ac{bux@m}N^C2t1nxJCWy_qFvHV(L07N!TV{ zA8!9{0T!TcUvfZThg*OkEd~h)JSIM`4E{(!KxGggpY(UD?~j@?=-~XS;hfi$e-xBK zFAx-wX(;GvDd?CfY1pZ#xu~fwQ&Wo2Q9v2VG+9YZIf?8q6S(jb+!erg6U1{D#Pj$w zJcaPif$J&sC)^jp1@IEWeISJEBaGuGj2$S99V~(sB7zlm{xd`b{1Q7z6gNm5KTwi5 z074cZOW_Zt@`KX(LYaM_oL*2~4;el;2%js2-&sb;9wKT5k+6`FHkXkxmytD-ku#H3 zFqc&_kyX70)v$u>1KIj8&b&Rz=6UW(2S zl$^bloqbeq`KsUY)4b)U?d-4T9AMxaV0Jgi!tK%ZJCU{yFCA^-T&}&lXO`q?lIo$A z?xp(Sfl{WIe5MyP^S(^heMpw4RF6{N^UGx+*z!?KU?cR z-TZ8CS)=>e-*!KK-Oe4}$QxWO=vyrAoh$8` zuI!$u=^Ab58gA_xxM(ngG~}YP4AKxV6$2p%PeUfBCcwnIxH7l8F~7bwzqvEFy+5~m zIJbX1e{eE?bh>c#ed+l7@(FAeJc#lXw(}i!@EvvxgZ)uhK83-KV6X!i><|V6@8Pe1 zLW1~m!XfzZfLVtTl?RUtKJhYz_>#jdz}`uvND83&#I~4Kqp#R7-aFDw>x>n=IXi& zQp_quL>t_Sa-^MG9ka-J%B`wrukyL^^JRS^EhiCqT(%8+@KxUId%EP+JeV;q7e>cY zj_X1)xf3F5Z|dxOY|bHG5%f&Frx(vdGlqlLTp+;>cGicC#3O46o$okFkHEu6ta8Sx zG`gH1%JU(*OJ|T$aXxTtk1rxAT>0vza%Zv@jn*Lvg@nF_UWqnfbrPBX4BI}E4GSiB zooD5DD{EE7SjCC|yxc`xn+>aM`JG3BQ!2H&Mze_c8sD*L%It06}X~Vsl_OhdGnVXQ8v3ywgg-`ZUOU_yz*^-QQ zK%2?Fv5)M@x-F+*+Rw`l(eP?xrgm{BR)&c3-rG)+q(@e+MJbGGv;H|R$e`%R(T^Yw zp<$m%MUwi+?9hX#^Eccvu4{e9ktr6b2wWAUi}Udx28jY@lgsuxWOvZE2`;WvFj`XlQ%r>)z1U{h<+n zgRi3pBV+sD#`ni2cgLr;C#ScjX18YNHx@w|b!};VePMHBc57pLcXM=abLe2R_i(H0 zXsh#htK($5?PRCzbhqPlx9fDb_jG^g`{C&Kqp9!5GvANrPL5ZOPu34lxA#u>_P-zP zem~v&e)3Pg`EnU*vD>N7t5pgi6yfp}!0k|qpny4^Hvj?|6mG%8uf~_a(0E&lZ>RoR z^zKMST)ut#J4qkR@92V{l6rP}6lL7cuA875BkVMCd$uo0?Ukm6vD@6hTiwDz{g$p} zqqn+IX$r&lEXDHeZ9kH_KxRH^k_Sot(@;9BG$fgiHmGC;wx~- z@cLG3(=)V@QKj6lg|2v>Hx1N#TQxpdkvQtE>II`gsX|!tmE4UrE=5<_8j8aSg~MZ+ z`fyZE=w5}XeJEfRy%}|PA%+?Si>!j}_CyU?)0djtwN19{?_tn(sa&yC)kUsgSK90Z zZIh=(K2F?t;P|cDV~0&ug|9Mm9nOpzvh|61tv*tp5g&O!p+UxDM{hEW6lF($kH^o6 zENUsc`#w)aP@1E~Qt7D_c~TIvY>zsht$FQ}5ZsXIum?zU)|0Ho=J|uX%g)vq0mxe= z<1A%C^=)^*NKMCwol&c3$d!XAQ?$Sv} zv2y9)o9Y1@`V@~_{7)gclT?0R-i*)rR^x@s)RPI;3bhE!41Dp6Wruh*fKo_3X9l@{58 zltt#XvOJ$;TeEJh2 z!r#9p3DFiV5h2hZq{9ud9~BT1TwGE-JTiQIasmPhu$A%KN(ONLFE+@2t&jcOBfEG* z4*apdSSY*reYkZ-0beLP-zj6Jre;20Wo4nGW2L8OWnciyW$fqH87DhCeE%IRzk|$_ zpC7Doh=_?vh>J^IL<3;M{d{u+d}Us&uU@QeTr6zBvw-tB;GZszX=`YJ-7y_4EwDbO ztF5i4s|z;B3@&yz{(8^-e?lmbocw>xB?C#o&mf?#uA%;XtNnbB4Bnmtdt{&{($WHY zA@FKQ8@vtoqY(EqJOw`4zyL@`!Q6LIf}5EI=_W`tS60{7H-P280bKvhts85vO z8?4BD=oDeOR#0Co8sUaXJVv=kC>*f-FuoK>0JX#oWD0^AKZb1FUiS4z+hCZ2m ze@aX?X(-E>Cu5kM@!Tno0_9Vuv06;BnXd+L><>P*Hn?7TW*|u#xBo%Nu8CIn}g#r7Z)Ed zufUZna2H%iNc=(-gYy;Vp&$Hw#c%8B@U<-HpLWy11&fQtbk+04bTwsV_-^|75erTA zUkh2fU?B@+egC_Y^)Jf~f8~FlFBY6{ivCy?1$&}@xswF~zb>$%{-3;HabbyreJv38 zfvoT6GCHVxfx8tKJ~#MW|N1Fs|U+tXgNLSGrcNj^)Y=*{lc)iG_7_(EF zm@Lpz%)B-MZ+^sN>V*SXDpmGRqY?L*MguGDv)QLMonvDX@@Jw|r+qtX6CQ7`oju&a zj+>kC{dP$-)~}Z!Y{EYPCu8yfwy^R<*=txL^WoaNz`zyf3 z1UVBKIr#ZEfJbrgG>#Gucp3*66D+iJtaRr}g7MEvf`=Dgy#j+cd>K?+N?bx(LQ+QZ z56Y1~ryL0UB8H-jtP+4M{Ai96^x|laf~o?%RiL4)qN$>)t*WM@rmm~5p$G3#X~TO| z+B$|hx`w)XKdMv)hQ>xlCg&UJ|M6nZ|5zmfw$Z^7dVW6GJO@kXaf903G`-a1;aF!T=>xcnxc47+lL413k&#e-5X9T33Sb-PVTytj_ZdR^Cr3F+?c?oOqp zJCsxrk?v0EknZjV0TEC_R8)}e20=PSI)&Hsyx^SE8Efse$JpN)V}D1l;rI#v-_IRa z#Pl`0Ef%icsNWa8oxJ@e&UaN#@Z(kkNoyjSn#jc$TnotNnc<>g(@_&$KgzB!d#H=w zbR6UX)$_XwPm7eDJd4%dzWHp&W07FZ037LS6)K8Z;bo^Uf0zsv+-4kr`5)APdQzxL zbFSZsM>#*hidP^lTTW0H^n8${qO@^8NyET%Me2@~B}K{&*9`^-<52z8w94#shKzUR zp7fb7%TbE69HiyeGF|QD*0MY|H?VVjr_ZqS0%%{ArkfC=PUa`EWDuvs-E}J~wA9pp zT#$r%ysV4xFk_>@2e-VaG2VPliwT=5ZbaVhZdD4|RQd@2G0 zYC=LVgn^EakqT@)uAMxtsi~4sbv$t90Ja=zvQW0EhMc^nyn>d3qPC)vj-t{XCFQ$H zDtDDt07>DRr=X{*uCJ!9udZRBu4$kFg`uVv6k6JbS~^DBIz~EojPBgMcUSkGuHJop zgZl=C4~*^^-@pIR*ckW){V(M^^Em%(gMTQuYe9-KWDGMwQ2vNZeG_%u8Z@lN$Y1n_NV%IMLzwA zCXVO@W#a=$5|n)Ew1nVCb4@;l<`hG`QM8Mok|a%V*g%_-Ioeb}CJujduVSUm_Z&Q>O3GVy9U;0+Evmgtd-!1#8&lk(T%OTsreP#^EVmCY&Gw_1leD-8@RF8c8R{S zscWW0+qSJ!+cJ7Nj_bZ5k!yDIbI}dgEsmFwr1ak!U)e=`bKb1Zv};28waOa7;{$oT z0kq0up`qbmVBr0xXS!ZyT~ECf`1q6rpcf?oE^MHhLJR6Y;@?-BAt4E2VJT5j>6;y)oFq4;-5N-6?epN6iPHaGyoJZzpff+%x}#_-e7To}e+vP`2s& z1N=vj)3Znu!^VT>$8ate$Kt1f-$zvTU*CT>}WZ`QG3qWSi6kg@A- z@(ZorH>D3E;7#jF)0E{dP@!WV{#pp+yCs^>)(gK?% zU~S9E!NJSTEx-p#_{ddUA zYsf472?{{@r1%qxN`N`531)RAWv#1mT}xF>TTLBI>^d5nceJ$bYU}9UxpS>~GBh*< zj_Fr|FfYjg8-2A4w`R!}q zLkWCp!MOhB{S`jEeGBmM-MhBdR;WV${rgWJK6F4w^pBm_19|(^H2&#o8vkP;@9YCJ zc^5!;KbXwBdj@)X2YP!4`}&5U^Z5{Pshk*}n4X>ivUec*1TG>!U28#Qye^3Uz7GEW zTTlwGO-4ZD3SARISO5QVc>h@t5C3J`$#TDu=a+)mi43u7;r(eb;?*)gKqa3Jl<=Rs z>S+5iguFr9S#QymD|wR?Xr9WogA@{h68>dHZ<$sV=q`_F$1CrFvD|Q%O%GU%hzo3C zJX58Xv==?9vI69Dis?YR2}>f*zRA%*{j+=B~i{%8FW;&i)XRM zw3+%M-m4@OiD={b;Em_BUDW+5A=Z`;Lf2!tQPD13D{T}V7|T8UR*w;axP^#Y6Q31d zjpaF)9D7wQUXy71a4uh0OdhD(c2-SBPIx`Kz0U9+S)FR&?K43lf%bOPFK?gRp_ToZ-o2f=@1EGPW{j}<{u(MTIf*nCPGh1V@i7mYpbK^KFu*+>_Q3N%k~ zNT?$8@jpmB=@X7vn&=akM9%1w#-N&~R;cFbr6mx77J8mBq-8u6VN6d*_hih7DDN^$ zF?)NKndD?3Z=T&fbs8J%!N^1><#Q}j8i9zwYLP5T$$Gyaf>vo=>Q}aFFYF{tAq>ag zYTa*_%>Sw8@8|LJ`kqNa0gjez%*;F-9D+PNpw$x>6qFPemKGJgeeM zjh*6uh`)D!fBT7&gv3usN-6%|GYs*DXIs>^IQor9UOTTU-M=h! zpb5OLbrLZDu!1bx4N3^WzjpnQ52L!cckTK?DHG8a4sHIJ?_3Gq`@(t6=YS2JnOZim zq2su=p=0>PhAx7C^G&=?9cA>UJ>}ghV|S+`rXB0aI3;Y@>v zP(^g)HdOGwfg1CAC*whDIMKIRdHeeU3{UBTvcB6@CqQlJ+@7grYc=h_jV64*J!aOL zEhdwMeL~@Hz_@&$&+**;)yx!iC_2SgDuTz5sY^o>WL@;#RcponB6xwF- zIgOzwM;UK;rx`7d4SC&H7kBjfY~P?3EO9fZrH!TkZgP{8jql{~H=5%Dt@1SAsaZ z_*He%-k-P}nTw=N5Tc7-Tydw1p^WKR3T1|r5rqF~6nzZa1mg)K1`Z;?1ru!hFo-d+ z$gy##aPVmH24&2#J}ANLfk9*htCQNhvtUC^^X~xyY%wD5$t8sG*>w=AoqF zp`zubqUEEe1K_8jzXF2*EdZk+9it#UvoI6;EjAu0P61gS5kO-9%39IBtzdewAQB z6@Ou6Z&4L@F%@Si6+2lK3uTo@S}OPTRrK#E>lrKSnkegw zujMGOWxF84*{DS=agZ%?S0s_N=g2ID>p9Y5jM1+J!0)&P|0fdD|1B5?~ z0eBh#5E~I08yOWB6&)8H17t`*gOr?@l#-mBmXey0mX?{Go|Tc2ospT7nVFN7m7A5F zo1L8pG)QT=C5d^@Lh@hwjr^;x5J>0`BNRPEeS z<-%s!(q7TZaq;SL$=XTT`pL76lj^T0)mtaep*XJHI)1TvRJ(apw{ZyYYHh!1W#`S} z*AHLTI%bx7rsn%5W(USU501_JilMRTE6fay{lxJ2%=jcA;m<5C&aSV|fBm|!v%9jl zzq)?_#lgY)!Qs}y!Oj7ci~(h1>;VAT4wyrsbqU&i{W638{JgG4AVPpy10yO3`Ds*v z{FN!@^+7~70ujgRF#c=7mEO|EVi1wrdDY2Mr1Mb`&|Au;;0t2U5)ezqw1duTtu_6| zoB2_eLGo-mZmV5xIqcaAMxr}*aY^C72LQVeuRGwM~HmaW7g#(C?KIyL9@DzbNf>Fh*$EJ(bkzDGT1PqAViL$qh7c8Gy6$#t`z1Vu zJiGOZ9i6o7N7EK-=A4Kyf51W7z3*<{W`B4=_#k@wMNI}esLvUIKy!tZpW!uA=iI01 zKqPq-70Aou$1ghKT?w$N1==XF50Vk6oVMR54Oyyibc8&Hhb?^|cS}JA`zD?oQ=BUP zzI?i$>56=u)dN?BxRAydMfu8Dv~4ncm%gEKPd0r5G_Z>rKV`|>3tk@!#}{>{3i>3X zlOD}IU$!K5i?EXj{rF9gvX_KQy^)>(^}cGhkB>5`ePGZ{AE2_JN;#|`1gY{ z{sL|&C8;KFWlGY^HJb=IE2mz4R(hmb)I)goLZ&RKc(!dd?s@w-dDeG<$+!Z1(g=%0 z-|3UYr#s;R+QE1zpDcBAo;1&clsea3XGmh8V4SlhCjMp_LAt?~EHml~3rEHVOqBI{ zE3FyZJMKd@xpP=l8SF014%|mIN`0%I+!^&v(Kyw z44JQYM#zy^f@9t$jVBSI>klzwbE1t5Iv78eW`Cj1-NAm)%Rgar&?mfBdC-6B5Lwq3y#F4S5b+CT<7XqoGvl09D*LqkAqWOQ(-wkQI99Vn^^> zmj=X;l5E{ZP$!pSB@i1gkB1R^RU06)?o8PypIxbJkLS(5SGmv2;fXgc_^;*rQ??{A z)-OgiP)T}c&^Aku=D?X%}%Z#NBy?{0LB3P{^@}lVk^>v)7>!n9jseK>(MYE4MzR@K->3^{(EEab1la= z-;MN$+_;w$G574bSl7Z*Vk{pkYO4e6GTgoD6UYT!PTg_DM-p6*g;+2D0(hU(Fvw|# z*I0Ol9Q7^oG|ADZ1H-tMX zAN^f?n8dESEu+mmx_jqG%JW^aT(*mR>_?Mm4F0rX3c>O>LOa_S-^Nj^xzJuBhqs|` zaz^Okn+#|;1~}=&2fX%|x`6OB=PH`qSU+8aaJ~rJLn(@qfWLveTq5U3O+(N}hfMDK zD(u$i3vxQ@X-eM`q}ydUC<)A@D}iR1V)P3X3gp3*xD8@?M^ZO*TJk9mv2Q-#{)*2h z3CCKq7vs1ah=qK(JfQVH13d{QK0C@l66OyAgAeRD3={bKPmw>sI1m|FBmmJI9+W}_ zr8JP^;Zy!545lR_p(i0_ASGiWqhO|>WTl{DqoiV|q~@Ta=A@$GqK1N-nihcP8Z@-L zP|*Dqv~;|*^ia?NF!0g?F!C}m@-i~b8rfBaf|Tq-sBg!B`72= zEFvi)Ds>Ya>#kBgpd5yu1O^4bUVsu9K;)0CqO6>f+;!g1l^yFpnXv-C)ISrZ?%e}n zKUYaVkIYO>LB@}jnYoRHg{_69y``0dm9?X_wUf1ti;bkBl)K_MXP2O9Jf@iZbT zA~Gg2D)#DWPKb$31c;4Gij7N-i%$WFPe=twNCZerOae$xN`@jiB?G+OscD&MX<6wR zSs9sE%HgZr9}xQU+ot4qWBuPZ$N#CZKJbWXff^z}{q&(1Ip{&|+DyOm6JS7gURh*a zxdL|g^g|aReM7zd!+isz{exruLt_I&;{(GJ0D~iwSC|?aodS>Y@YwXo_{`Yk?D*8& zxFGS9Hf)!B(Q#mb+XEqwpHpFE337Z9WB(ARf&>W_#ty^*DFqPqe#RxL1R`f zX;!}&xcBierDR5-fte6%?13*0Bv}vw+(W%*lT~o-cO*}^vYz&vOGIK|e8`nV9<1of zQ8g4?uDZigLphoE%6{fYS`(|BLv>X}*pI2gw(ku)eiiiiurKb;f3BEZ5w{!ed2maE z1Flr{liGdWoeu25I|RDs-ADVYOcIc#c1pKTmIm&3sc;isgk;Arx+DPYdKgi|ni`Lu(o$NjiKLi@8x0;Ney zCx$xXCA>ThquP6gm>(|iNJIkNn7A%iaq-DlRVpnZDIJuk#N>>m*Z{B`* zcbz~4$lHLh-8<0JH`v=h)H^WTH#iEdRm0F)^?RWj9G$wt^w202*A;7Y^7F*>+|=j! znYqQe`Q`b=mBr=t72v!N{HZ|t2XNSj-n;(grRy(?kL&+}0tV<=fTQr+?&EsP`wzA2 z_fZ2{x`NhSjy{~vB#=Y zuG4fkOYRl0JAI5}WlL)IF-`wRxX5kg3HHgVg7py6!uF5E4}QcHQ|LA9&Xy=|I7+T? zY32%1%vmI!4p{5FeE6~dV1grr(JvS#xxV*c*Pv=%y#Cvo4Ij!xLgz+XHV|iyO%n3)}^~GE_lX})!Go&p|$4Y zgQ+HWuJngT_9EIfh3XGHFQvsEeto<%W+LuYeSAhQPd$yW^2H6sRR~+iF5D^4Z{A-r z!QlqV2&@NIPDdXq-di&bR$Al4e7AOV*!VC+8m%B9I;~OO4;(gdT7+M&HzcgUbyhUKj2i&PZ*&@QiA-ghX0nRKm z&@BpdkwQ;E#6So@%t%DSL`=#|O2$G)&PqnXMo!620YJrZ1x^YoPReUgb5c@s0Z@Ui zj0W10T{mO2^n7#x4E*$r0t`$7j7)-z%uq0~2mvs&Lczi&%*rOr#xBgxA&(T- z$f%eYkh2&cpOBQ8n34q6BPr?77ArkFJtHS0GdD9c4_c?P3$n5cvvUB7asYCRuaTQu zoSRnykOzX-fPKdeiBX~2XBwXl_MKpE@Y zS}ErD33-57L0+r{q-3S6Q}bcCEo4({T|sO9h9~5LhgmT}N-6oqSeI$};N$LOxW}7C zii3>3S)yZm(y3_nxK!Lj;$|_b4BY|&Dt=YDynN2?SVaaDg z^KPp-xCii;RZJ>k<1Kby5z#Z&?el*uBTr*EH9M`KSLdMBuQDc`Y@jxTU~^%UAWXLm zF;(6Tdef@QSVDuO>T6s_bBxl~i8=YTBUd_p6emwINH{>t`UZ>8AbtZovj&AZaoHfh zs8Lw9v^{kJjg&U`B8|*w%X?YDOAMV(GRh~7*wn(zBVPpVTQpUp2+~1ys=1Y@%K!c_ zz%WYnj!Pu5RMxxE^5_+23XS^|-DB+XFIU`Je}NZtuu#;%;i+&@Jw|3~*VFx)>l(mymh zI6O8qGBG?lIWj&qIx#&qIWs;5+O65inYroDUuI_KKhG`9&4V!!G+k>etH2!;cu4^l zDWIg=1Ho%hTr-cZ$VYp7Q0CG8zi7sO@5KI?AfXM|&pZ3|^$BcyuRpH)F0Rwxj_b57 z*rs_O!mzRK&rg7q*f2a`N75N@EEi32^T`d8;YEc62A+Mxu38hN5GLJbAC~xH)g*qg zxq1;kbCr}6ueTQ{Iu?or?C(hknw-rWiizTurmMj1&g|i#n|Z*dXJr`RQ2nLs;u|DVI_hy!BjV+-fF+l|%mY?Et))!8 zy>@C^K}^VFVjijXeT%!1y;u~SgvmdKO+VP&NoW0_#r0$gO^LzSmt)NbdD>}xHrM+E za`L@+cd1T>Rsyo04$We_Y0vpa^e}0>&QOQKM`S(x<5Pa`ADEhrr+c3X3aGH2T<&jw zOK*s!dLn*#xU#b}@cepCVHzzPTAJXS7t4nA%`3FjBN>S`pw za!K@-%q>tp#O1^#6vQPJC8U%krBx)QRi$Lqq-4~kZmUb*)__7*^9ourvcEx2OXe5c zmeYblUK>CbKtWqU`HqU3o|=Y%Ch(-Y^WcuIv9A6j!+T~AP3_IB+$?Q9t?a$69ek`E ze61ayT*1%U@o#YQv%bdFuWX$B0BoK79Gv`|o%}qU{Cu7Kf}H)roc$tP{Gwd_BisYR zJ%U0!gM&Om0z5+fJwkmw!n{15dU!;*dPF*TMB97B*m}fXACK*LjehAbL(^)Wrd7wLmM5i_ zWTfO5rDj&ACcR3Hd7B#kAuY5mJ)|Q&xGN*DCnK;oGq67^a3DKqASbv#Hx$Im3=~EV zm&T4%B#zajj@IY&zAAg)So;QCtY5!<^|tNB`<|MQ{WWbvRqbOnos)IlQ?GiboBC&3 z24~+5&AlI<{Wv_^J~G=eGTSx!xo2#)Z+v!OVs>zHZg~33$mjX7xyAAMrP0OZ;pNq# zmG!~(&EAde_RYPP&BIrl$93x`FV;?~mQTP()5h_KuZR8L_NR7sX1BK&w!SWHY%OnW zt*mdYtuKGwnBLhO+ua)5+n(9qS=!&*1RixjP6LiZS2)`Lg|7$-3g9b(`TLzR zJdkHvo!o9~jVRWa4Q1W(#k|2#JFTwaNGV+_w@c10hJ~c>b;38ARhia^!$0Pd*OVuq zFWyENsYY77Wx5h@Fo<&}U?orK4cj2=IKM#?C4~<+i&RNWC?t}B$yB|CPfBi+G0w4* zKNcauby;VAirC)-POUgxGxY^Fy^L4|k^lS22N6t@vrWXO*|-at6U z$7JrkvDQ5HwU`icswohkE@`SXuZNQjD!JgInBtG~hcvOBbeG!Ce=rwkeR50WBqF<- z`eW_JtF9ETj;@S!5dyQ~$e|TH{V=0PaenEK-Hlwc(nGQh+Ac?l?U5it8tiZonY;k9 zKL03*zDDB*iqiq6spfpeA-WpLlEjJ*X2s0J5qUh5Qxdr$a(GhD_4MLISa6pOBMaU% z;-GGxJ{mxx^U^W7M}N( zlBF62mkuKSrV+m_bfJW#V3@_VT>o|2IMEl&QY$x2DDOT>$51LSw1kg;nIn~Zgi@9p z2$3nv=YP{Qh~PYI$t2&DtSwI(AD&z${joS7Ee?|R24yRC9OWd>!n9zKPU;!ss8tC; zfT($Pp;Z!Dut1dzs@ar|PQTq_M&T04ib{bvi`ayxRZ`u&TkQ3OC~Q_QNGQHzX$PO; zE9=lXhQzR^=^UfUZiX|Km0QveN8Vh&9meWT3=zc0kLHN5zN4!}+jn!hyr)|5+XptT zH^=wszabb#waW7R@R9i@y}EdFBk^VmiB?5&vYAN|+&z?dh6V#RdK+vO+5Lhqii~#~ z^Y6DRie)>*hoBfS7jASQrhiwDjqK;0kO?%URekI=m@G$!hf+QRG44`Ex^QEjffM>2Zarj7$n<9vZk5Sx7@2ob%*5c;q`QqEAj*vOK)FuTiQEaemA zZ>LMhIO1QkHuJD!B{s|Qv~KN5e)CjH7*T9O&3}@orj+FlIoqTNvHP$tW}hRvRnJz0 z$+?J4Z^x0pSXH$6v0L1GyA6vU4dqr@%?ruJ(9Mg3wt}Sd-S!F25Dv)TNEfQ##mL3M zyu9D=;i&GrbEH&lOeBIYK|3v{jPVcNp2fi@goyP|3@n@SxMO`@AYn`1yjeb+nFF>@$2=^u46}5r z?IOY#;rs(poG^mgnh>g`ApB)|89$AD!Y;uU8q7!naCwU(;nG8aNiA!pc@l-@6-Ps` ziPy#;y7#DW9*z*R^PQN(dqx%0MFNRcBqIEezMJ!KM6Ix&xKSOW=oW9|mT{w*mcf&} z+J!%HSo|dP!SVLXPB=^*Zg>`(z-S+rB`Tt)@#4Ju@#r*PBrcE@71Y$JlPc-ydQuft zt!fiNOKRNbf^O{^O|h4W<`t6V6_)1{QQ*I+#D7y+KukqITwPE? zQ&2)nP(oW!@{XXSuAroz5EKT&l6u0Dx+0QyMWycEl)ft_eMekINAk9g^lk0ivfA=; z+DdZT>Iyn{6m{+?={!=_u~gP^RMz%U(ehK#3{=((Rn~Z_to}Q~Rn!5(R5U_WHGu9U7k3*`nkCIySfLs0eA$u19%2{ zcm{cRhq(KOyZc4D`Ny~gCAfy9xP)i9MCLg~7dggNI3zqbPkQw*`Hgq>FzOST(Mwi-{im`XOg#$>YDM6&rKAWbkKAaEv0y(o|k!{JkFOM9`@`jk+{ z=`h@ZPBG#1IK;3^Ne)wmO*coU(_vX9w%o|+HOkO(tD21Y&kG24&QLF5 zPu0Q?JTY3t#k={WE~1f~sphea{b|L+{L^qdnSfH>&p}9B@Di#Uo3Mn;deki0H|}Y^ zdRDYx^i;zr;YhI}@K!&;=Ek^KGygkQj2}v-PWmZ5$_VOBU(FiqDp0c)R^~WJYj&D% z1l(jlPs-bSC4L5x>ag{2w;%1}A{vD}UvCKf;(mT1bidhpZhYtTrPlYJ~s#l~YCzq*HE)J*#>`*I}YE%AL4r=NAELr6$n z^-SL9dl<-kxh<%VnXDJ8$${K`5POTv22P#HujR#ive^ax1&Mm*PvhRW_h2V5lQ%3# z19y>iV8wQW@uZn{k>NTO*_3WanA}VX=(2|2exyt!*=UOFY*22FmSW)KrjB&f8Wf9E za7So5@5X{+ZVsixaV+AAhWvVAs8igE*i$o+Y|q&KU@UGdOdTpNeF)vMgiNRx%ae4k z`YuE%k6lCoh^HJ8YdL(n5=BM1oM{`^E_ij2{K#3YtJ;R$t*a?52TOCDh&6Ky;QFkd z6?U2}%M;grSc*iR5_hS{8BU^KnWg{K`gNb$HWQ$vX5 zGir4Q${U*SP9&0{r(MkQ%Y9@I=Hd4w16m&Lck$5OP(T?my%-R)Z^6Or6*F(^!j`kq z!VsUh*hi|ld64BI{U#hX7>Tpd=dfQlS4Uv@p*O!^_2Wbx*J+!E?c*7T4vv%0E)%vV zvmR?=_*8;EC)+1q{E;|M=Yxr2Fr-77s!tc6()FJ%#c{=G{9@Ze4BG-j4nqx73JuXl z!9fMt*%(9^n51A1#Dva)RM9L>_=fpD347un?icVhFeox8I4U^g3ZYTKq0u1#VbP%g;W1(1fJ7L3#UPBkVi1Ch zU&0lG5V|5yPEJitP0z^4$jZvf&d$!sxgsFs<>dno!vCBh)1Ph2|6vIZB5Z!&g98rp zuS@X%#I|J?_$Yt{Ip8%ziOp9*U7OGyIWTJic1=4_VH?z|>1tOF6}J5x0)OKr{KdKn zx=OnCYr0w@{beTn{lO59!;AOZnZtqFwlJ~{zPdr%jfF zv~(O5^h~GzK{1Jtq2_`qAX7RGS%q#Z)I)I+Hk^+AXZ)hk3(dg#=svjT>OFY z{YBRSKi%NXaq4?NMqoVFnuN3++{L;|d)}n~@Yt5j`Oc6f-8-;tdx=Xl#=@)X|J)X8`tV@*oP&!y# zepv>Df|qiH%k<%$q!^g|3Rpcw?rNmgKH3YC1;l_vFc|kpl{;Ycm`0|-Md}8+6u;)v zFd>{@x7oa)OGSXigD-`-K?rk$0Okfh%ne*v1T0ttOjraAxEpA2H&EcKDE;9%k5 zVBuh4fcOID8gMW$@Gvk4Ft8}Fu!L~1=>1fa6>A?8Ji89ccB*Y0n%nmQy8aLb$=cxtuQ!}iH$C#0iFr!Q`q90WJ+S)jVXNk-u$*hH`9C_(nS(!XpSpvCvqPfM=IpuQMl}cIFDp@sZSuZp)YPE7| zHS=pVi)uAWYt<`jRcq^%8(%23JXd^MrTDQzp|e!Jw?uxpSbnlZexg)VpOaIslN0ZgQ{S^Qzq7Nz zYn-14o}UMO{|*p*aRJ5UWys}aCShEi*{`Hxr2_$jHACb|{4<@o3Lqe6`aoa6@pj?xZTD@G5s*f0 zrHX8uIr^NxNoqz3un4k;ngi~!jga-%IV^Si5_6Exyxwhllikzs+{nUeZ|+EN{Q1I1 zy-a!vvsH?nZ59AE6wX~9Kvq9OOE9@L^c>r2aP4~t7S$zwRUU=>Qcu(xp^JUeXfT@ z4s;uex^BIC6aC=bsNu;$+f>hwjVD#^htEmh?B9C#LoIEmeqWL9hMH54Fz+nV09$PR zK{)LV(E^k}g;7_JVT7{h1go-AH7}{%%)<^> zRzWR^kC8Yl3NXG|t8q%;#zCl$bL*)N6aCFKnlw6b$Z28dW4XI5@fq1j7Rh=S7oAwX-!p(yilmpG%JP_=#i=!`X8CErfZPISE8tA?)s3FWKo@q=va9% zdd1;JTZrJLs=XCqX0j@Jfz0{M=T1pM7OtsW1P@QSX!-tAzJS0NT3LbkXU_R!&2IX$ZXqH1lIv{3{bEAg#tM)l!rKPos{W|J{BTL!1g3Fo)pe8!*c`}n!U z*?D61+wSw8a{j}&N@%q_s%*+>ANJRk(pZ@*x%pe(IMF_`U$)5+l_10-@mn- zeEYuBio|uX+eT!6vDeA;{9?aXXy@W!P@d}&Y@F;bk0#BZUmkyU-?==Q595NIE~nZ< z&eltxL(ac8>_EQnc5wZ;IGnKmae2D-{0HRXWaq~Z81y_CB!yNu(p`UKl{`42&Q?Ux z-2m*Uyc?=z?fjjvY_9N;bb2>b zxHlj3ny?%Okwh=R4pV5OCEW{GP$|Go1%zeMy{8&c1^A^3?W_iS5qiA^gbkhToL+m8 z#^{B_9SWa#Gxnk^R0>HaIzI_E??pRA6_T$hbcjyx#d!1ji6*T9qWgIpMlGD#$*!9dc(JY>}}X`=4I zP|?GD?C7%FOiDu$28RX2ePwb&-9s^6hlSJ_>alKyCn z`EnF6lITnN1kV`qWf7`3@6@K;er7~K$v1u;`&`{4FDTq1F* z!N9f#3b?p4ZzGBCPK;C^VLw8Ev?7UumZox{!oilE;G4hy6a*el3t7(Uv2wsPE_`+N zij@(Wb%@kx%FAN0sL4*A()j6!Dw~0guK?$M<+(1|qZK$qvS*(g32B>u__MJx z56+)+62rmAeG|aF5cz={Oj3S_+pF&NR8y!&Q>)lF$+n8wmv%vI26Z*#q+|#Xak{pG zAVe;9sZhK!qK00JN*@H?iRT5As5S%&Ud4lV8qMH8!5n`LsWQeUch|$e0qc(}4r98C zS=8A`fb5*eZg7O_S*3p0t~ZGc-GG&-tviPfA*9I|y(*xwCts{?Jc`|{BSGZN13A8= z$DdbOCz73q{GEfzFokVa_M7H)zFJpZdjTy>Bd)nhSA15eMdO;~edK z`D&NE^msD@Ytexlnun|Owjrj-H(K5aaPw78-)~$kfFnseO#6XmA=V)JNh^n^5{cHj zPvE@f?Mt|rFJyCTF=MDcCHyZqwwI-tUJquaaIU50qY>c~!Q58}O`fpQ$4U~c>FzwP zLDKrFTsJ*qGv`uyv#QDen%a*64St1zL4zqnK|(A&IlnkQzxWP4L4kOXb=&9`rU9^DvUJnCKEpIqNr-rU^y3RscA>~?u|9mwFn zEG#d8*pt;wDAu5u-d!6!TNpZD9y(tc`@Xhvd9n_HEM7upAduGg}r&llsP z7XwQdJqH&b&n}xUAypUOpC4U5|N6aZ_ON8}vS1yOvwWGbb{;hM-D~E1$PgrA5E9jO z5%p=$^Yx*1{i$Z%@vRSgdM#THFPERZnhAI@Y*#k$Fu&g@r%x+;oV8#IK5q;*_AQL{ z3wX!(3{Jz+Zd+Q9ziFs%Xeg{1$*sHJ-trOK3YFN(Qr@W4UoCUk%<=u&61+Ycwe%%z z;d9mG;Jcx|j^63M)^AfUj%F*4#|ppqW?uHE91JBbevaLl47u#JJ$-As_s;%Xi{DmL z^tall>s6skWqxyo-V<5gL+L)_8O~Gr#-9tdb_?Wpazz#+x#zvvR;;MkT~OD;;O0CK zra>NnC+e6RZo3tF^L>m86@p4}yhbk4Nk;lv28LOFuFqVuGaUD4m|doM+^2M0r#y^5 z`#H@8czyPWnRd>f^eG*WEF4NL`c$0V@~rgLi&wREudALn7FSee78GUV7bfKvM5Y(S zr(~ri#O6na)`a^thd*fw@qX!V_0sp@b8qc3JL3W;=Uk^J8O~3VU45cGT|!)){M>v3 zT>L$K!dyd=oFelqBWfOnHs}Vvx*J?+>{)K)xWEza!XM|N&AuVT_@H1_AJpZ$Vo29A@=AN+K9&T zsA{^H^mf=}dgRQqbR0LSScE8<1qmqFv4~ixfzcJ94BS8gW7@y%Y=6J6{_YA>)D^-v zJZsp+n>KhRi=KEI*qAKO??7g!zcqO4 zqVhQryQ-udElADszncH@%xo^*t$kONsg6aZevu9q+~)s~3?p z5(OfknSTiZ5rhk&m`7S|zE1 zQQGH~y?Ha1KE#B^>m>y9eiNXKKO#X%h?VS4!=zFX+*sC2WO1Xl?a#kK{Q%3UBOdK* z+ou?xOR=>5V!f|??$9yM}os@M1eHA@v z*+gKv5Zmd;WF37XQq%5IA$zudaUL;E*GBQ}KvYfDEY?-!zm??J4d<@6cJ^N2G8cCBi`t*>>1a%|i6qj%Amot`y?k6ObqRB))^ zFVmJ6##XZ?Mk8xB({ff4wsoXtc}1}3Hb@or8~f@%CGcu4`xq&63zdGeu;`n`w&YGp zcS7s7+KrDKD`_(3Z>!)Uq(l~q>4FMUSxmRY8ade*<>9rN_dDG^z_rtwGiMrSL-JZyUsDQOy3_;sJ!PyHYu zw3mved(UBm#v_iUxx4D5WKt{qLeD2WztCw+B0gf+oN}nM8}6{bDPH@TcUU#&Ww^l3 zh2)*cZ4Kc!YDOXp{;LV}{Lvzpmy5fBDlgVa-eX5=i&C_+-lxTl0=g(-(42c;5$zBJaQSZsA}I*VlZ?{1MYaqeHkYNYRm( zDUs+ioFE&`!Z$eLDRSMLqR zAD!(@3|XfZeSNt2Ku)^x=ay^Js!u21lN0qx5^dxe6j`j}p)WIq-N?u3w>lu*Eutg3 z80xp3eAq^`zUimCK!>-jkib+iWXAfMVaSJ=%wiNhgl{sJaSy2{G>=Pv6O96L+}4wv z1|QE(Gv2bQ_wG|wYzk?@iok>(H3O|iK*%4Rts#jLzgFRE*<;h}8#o;k)F_EWHMmLe zkaS#FJa<^5@L{X;#9T=en9RU%y1>|l94>KUQSsX!lf6do-mNe14NqxH@r*L$JW zn!*#&#w9^?+8aoFSV!IQIda}?O7qr&t=ytQv@j{3YS5r&Ds2HXmSF`>cHp4vI{MGf zRv_-%f^1UlN`G2-P57~fmE=ko+7}#CSAv;-iHs)VV4qUz)j`_N=;kajIZ15dwyYXl z=bfUjlp-QbS%#X^NYh@777#01mL@c5OZmERVc_1~7_AsHxHD2kuf!%STcDRsar7cC zy3&+MdCGcWnQ(U+m+oPi=xVvr6VuuX$(oEiXpUpS$N_hxZXqwI#yb^7hSK~Rkx`B(iti0e9i1=3Ej z)oWQ6l{rHSSDQ*EtU6uqHf51C;_$}Ka?-^bBg(Wm)%I_SVSynZc}ytOsEv+!(|*W5 z>ozzaPV28P5mK#2Y})ISNNyFCLWyQ~AIoMPo{=zB5pK72%_;A*W7B6!)++f#j$Tc< zb-HV*y&q7b@U*Tl=4+s4=_=I_ae~TQ))pb<$B2sxL~4YhRH%pFDi}Z2R{WsBL^p1@ zT~uZ0zS2B)Kj?5GIN9Gb)|rv`@cg#=_aV=Nt}vPjgS3ux4c3u2da+r@lr>`lTgFNB z-uSXr<1Wtp@wk$fTh7A77CKU+0r20D-KhCq;=2668Be?A*?4xhqZ^Xqi)~al;ESM7 zA>Tzb7+%Wgc`K$1!A#>=5o|^JWu)xA?^lyB18S71;2p2@zS=@}czt;;B zX3!iG*3;8HCY?lG#%l7mD~i#K468pq>0rcHmn4l|Zc#bAiKmaSXArHx-Z2A!WJF*%VwW4YkV^|trxAD zNw0Q|iA;W4DmE=xYW-C{y%2E#^QP2?1-o738>HO_&TBiInK>G*)O`AzZSKGv7 z(Fp-oVtj-rR^InV9@-qfGp@0(e+h9@p6_QOJV!*O$_eJp55zf1qKHxkk@W6ABw1M| zUCP~Vozy%M&~Y6^($tRUZtpOBOADLHbj#QryOJqL=Dpj1ko}n%i>)rdLzoNFf|%io zZ#|JK0y7@72igxrPi_nG@YE24#t@^)kh}tqS8(=PX`+ENp0x{VwzrLVMp@I5tdV%E z5JNN+XaX#YtPzI1JRiA;(b$j+`gYXO7S#sX>xwV%dfU5WK{`4Z8756}=f%qp0)^#$ z*j#)Jl6?s71gsu2dl3gAzJJhU>c-p1&w$Gy_sTti*PF3cV6g$m_VDIvL$K#Z|E;iK zv^B>i$w)R>?{;l$gpr7fP(M{#8Us2!SZ3?61OAno;9GDeaIjJJC5$Csq3U%_VB$OOQZPWAH%&zvp5Os5DFfU33@IUSkLr-098P$ zzb}G#cRh$wZxe(GG=$`sNF;}gf`*Pg;aTy=PEmM{2EmTufq<*%g%BB$5;>6+85+@a zF#**R2r?nJcY(9Pfti##&ZsYKI8_=}9RtNkVUkO z2AF%jLX%j?5g#LZqj!F(GLr1~c9-Hw43QczC_+TJhef7zGeH3uV|f*~et)QN>JS5C zXb`g4BvmF5m&Z9BgCBS|5CY~*S`;u?iH%vqm3-KYW44!Rc7rRXiVDe#1qpIWlQq)C ziB=aAE=P}BXO9j6DcrbPs<$*{=Z}WC7bfRtZCQJpNobo?e69zH1$hwq1)5t_k-|Bg z#95ri85YsBg2=>sA-Qmf18T)XhK$IDI-_}JHV)f{RZRZmi;wn-0^x`t#U95dFj1%% z3DFzlIgD`!4eU8W*!PX>S!y8JdG>HyE}#N_Sc~(iH&{i7lY?Z@z@A-}UCI%kIl?5| z17ST8a9EQL;+b}g5rdsqM1a*^G*(0odWxpWmD~cDD&PV$&~tn7PhqJMr?o@^Lyo+q zS(}*?*F{BqP>lDGaWIh!@rjZvCJyl#Trkl^DR&Dn00VpgO+z6AF|m=dX^y5g5YR9a z5BPdLky7ap4fRGvxBzwfS%FxpIz}oJh>1_3X_#vAF@pwIuta0L<)C}HeR6pt4{Dk# z_NE3wqDUue1*49~@usQ> zdP(i5crjt9?U`Nnps4a0cXrwnMoDzN@ioFXhK6W-q#BQccbu%+s;>H~uqqMB8HSpv zh^3>1JQ+#zrZFwESk@^J7eJ||DT_O$ZbZ5gC>UyDVmRp_Wso+c*~y4mvU+}bU&1&L z)i@Jgr#ekW5Z0!tR6-Dp=R7bc6aEQG(Lezs=9Y!gsN^$2;FO?kQj;YpW#9s>*%fYL z7MqIq4;OgdzuYJu$A2nl>pl z5b{zGp$dJdHxNQef-q=myvjT2xf=Dc{&59^KK-LsC94=TDiE%vBt5aN3p0|XRURmd zFI@ExVQM89d4JUQ59$RZt0$Mmmuxr(P1u(UQ+9jpv9zX&Pj{Mj_K*um33a7fPf$B+ zo~k7(z;F&lSO>&D?%IBj+K=%ZJl>z197uO1PyUpcU3zO>3X$V zx3yhMkH@F1%M`1O+qjPVxL%l?Uz&sx0FJrnVb4jo5)iaU^pfsUa}DW`^s%q~x^e@d zv2A;Yl2Z_2ibdM@54uR2EU9(Bm!kM|w*tY80wGFRheM*96#H6Yg+jCDNMxP2DmX7yZnk1 zBPT;r`xvB4mPWRx@_4$xxI+jNxHL+uF(GE?S*pr8KF0?f62w&zo4eW<4V{@f<=Sd$ zGQN8;Qv~sVImEI>vbO6|G2LsmQvqYI7kjR|ZM-=Zbmga)+H?!7HzS-B9z2r4E4Df~ zf!2r=K83+6QlC449;nohw{;n&hHFG=x7~`jocUc_N;OefW3r~50Srnj9K!fizQxK@ zkXyx8e8pHiRLR-I=mitK$6HG>Xqp?e>Og}Ht1DF)x;(obq|3f)G!>=Uodz*OVD)*n zqMkFc!KGLv6U(iHGnoE`}DZHRJ9am_<1}{rAxjEF!Fj0CdafN66KV)hU`iIAmVXzpdJgh5g z+&2)@ip{ULZ5BKgyPQv5niP1NwX>{#Q(abZn(G*?L7QI1R9EK-YnJFvAs%e13mAHNN zT%hZEM#v;>EdCyEjKFoZZv~NtDK{7&bIdcLyKJgaw>!k+j87ewHho-;wd{x80%*;f zZj*^xjl8@dIh73CBruI_1JTCZW{%WbA_`5?o%hWVSXX9TAAqdDa)=Z%D#!emn*%1X zDhh#x@dArC9eTPK|AKIxN6P|n&U2?PeJZ-50@k>?65ohV=3jg=kfDeYo7~4nr|P_I zOIMZ`{yp8)UES6#G_jnYCIk~ElZz*~b>$PZJnOkworh&xB`F=(b~uj*q7W{5G&fft zFHleev4Oah(?!CaT6x)v+-$I?Oni81rmMcG0?Y>py9DyyiqyvAEf7yFdk_T z$+q=fC1b55FBwUe$jT4q%6^A$Pe{45IGP$4xQ+@{sz_tUY`MDNhy!7APrAlI$7{xz ziailp%MH>TiOl7}oh$*w1!31>I}rLUxI+Lu&DcD5* zm3G~J{^x)m=z9{&rgkP@yfkidn@dwZcf4x73QlPp8|q-M(;KT0o2#Lk%g3A^)yJB$MFNklgb==d zWc6L)YQB{iJ~tb_pJa{6vCb$T-o6zm)TRE>#Mp`T%igG%HO_eG2nd%q%mR1P5nuScjX=psMzBwzmWr4h@! zta#{xh#QkYNk{GT4RW(N&BYXY4tn#~ksYf#F!rDV1oo?&j(we8P0W(ldtA5u4jH7L z;x**6PzMj6i?t_p~jQf4zOp@WTT-Hu@toHyv zzk(|en7c>psHbjCbdBx%9mT3>%R*o4-J&s%_t=A(aqfhBrU@QvJel}Mx`jz;1j4tR zvg7?5imFOD_Ri#U>Er%*q-&im+LI87+h)k9DLuV-Rp>y_q1CWKKXswv+C53SS)At zEQ9p|KK??_^~)(H0P%v|KY|6>y(pOQ-wSmL|AmuKFx)+g6&+T@C{e;jjvYO6oQP2* z#ajtKo|F)6k$uahAu5+U$vyBp>4l(NpB2r0co`f<=DaqpqNT_av>7(yv ziV;VYkhIYzFF86+Bp=s1bG;@ps_jT72U19}^|nm@sIt&(6h}%up){+-j|5cBOblL% zX{O}F{71)od~v~}?FcdvvPb`^Z7JwpqjXPAXXr>rXuQHGD_s!d>MOA7;;l>W_@F>3 zEB~phHS-8E2ZK>Dy3tR~IK1>Q6cdV7g2v)ptfPYH)X1{AoDvc;)y@k}G=i)P^e8H; z-E3FapgnTUquMNPhG^`$sG@@k+NjIXI1O&NI*U^XgK`{F2T@)#>Mo)rqgqltOtY0J z%s+W`uUfFEjA&cfffFxMGVh#yDe*H|Drwk3R-EWRXXHPs4;@P)DS6 z$Q@0F^N@=at!0ZGr%`x$n^949({uQ}yP*C`$KHkkvy!57Um7-{bX+E=Xd{hn@?AF} z&8uGwf1c?HD8B%`jvA-CaDcFdBl!^~y2_j6%8L+OXoIwxwbMmlPdI_42_I5~ie*-^u=GU5a z^Vx;Dmi8ZN$MXnV&$OL2+iqvGC~tHx-~$?#cdE$mp{iz=Yj=}<$!ya8zI5951*6=4 zhOvWI_|pvMj6=lYuZ$opQQg}f*ZzivIFdEcfe(aW1SL2@3Rcj97sOx&HMqeGnhZ?8 zB8c(M^DjtgCVvSr-OW7Wf?SBDQ@x@{j2Nc2_b7@cw`g3~I6?tFMB@b;*;|-~b}xl+ zMKDf--uH^stPf(UU+dGI0!O#2f8fGKyu%1CfLEo-BqSO)(;?z0M-!oiQ3A+gTN#O% z5F@@xDB=s@OVIWYoD|7On-Lfl10$hZt;83<(;?&vf+ftQQA^G%A!w?n7|<{zFTRuF z79le@?mVX=a{7m4qGAtREGv=Cn+YS06{y-V%!-)0M=qRq4-x6gO^O>HW`0vRj-;s| zyV%v$>?ow>VJ3-qEE0O|{uIR{87zSu(H@oV^{?36jZ#0n-RNB4Bax5@iqW}WxaPt< zha4v%wYu3Zs?$FI@r#@zB;jWWXc0)>PF^IO$~vv}Cxv+rjFOBb2lcs6e)iL!{{(12 z1v*fI7Sx~#@}Q+a$VzwxP$4m!9q66{jTz(+Cgd~UP&}eDDo!z)@A=wc>IF#N%s>}v zsf{>C!j2Ug3kJAJ2rk@&MAE@DG{ML%AtZ)vLu^aq!$ci)U#`* zPmLb+%Sx51keAv~rh;)M9RHD(zLoKmBxK@?kkV2XG1WmaVBJdUb%C^9U|wC_$lVy~ zkzB}fbHl_`tP%qLHdI~}jC-t>Jt%pPGl2?I<2nke*25B7azvL-(W|olg+-&9QEiIS znwRL7(5<3Kb`9$;H_Xz8K;&4b2Z$HZ6s-UQ0{iuyWa(Gc*Q$j z@|M@U5#+3FK*-1uI^?cZ?95t{lMZu;Lmk_k+(u!zsE*?Ep6m(gNYddJcB~AAw1t~U z*18cMrZpkzh{4+sSz4^x^l1qpOOb$cGS(>s8r47{7F7+kH$v>-M3vh|q!4_hmZ zUCT`;WX7l!Uqm?5zNlY8`m_;J+S5&`7M*|3yKQcF+uPp;ceuqpZgO`ly-oes7aMBF z_x@5KN#!z3eqZR`{%*I1`wg2xd^|_3q+?||*-3O+{7d!zi-`nG-paaER}$`o9lF3p z#C>6oWg+aV7!ik0gDN{92IVF8@OMirdy+9qZcNiTjKBBWYmX4=%sy7gUEmG!q8ePk z>rsbKBCd;YEDNHpJkZ5s+3;A2<0a|f1uk+i5P<(&0;5`w%6IeFnW7Fx-W`aN|g9{(qZ?g!#4g@ocWnME+NRzHqDZne_CeX*%eQZ zS*KA)#F*DQHFlXP+f_faHxx-nG;T2i%B$d{@?~uHHVrF<=i2YGx=1)8j#w0R{+-vr z>O3&BL*w{@eB~)3E47&$O>Aq|tmVdi?sdO=-uK@3zXv{gNz%<;4U*Q&L()e>3h#Jp zblwlG_wC;^=ArkvBQJFiRQ>@CpGp|;jkNH=o!f926~qM|Z_h}m1mswi{2wa%)4P#V zSy+Ebh$rVrRjYp#?zbFH2NoR(a69zzmCuv8v70}UaJt|z5DHMK{e!EG(4vw-5vMB? zc-xTFVGos&v!`1uxyd9?vI?$|mko?JjzcndV+R-DnLV;DHbICNlZ>~sGlhyT%-nG7x~|))y@;5t#{(f6{5c?-v@RJq82bm5VLt5>wnIYxKtt&g{+XVR zxVR~#3qnJtmmm)t943_zRVaXvEH zvP7C8>ASc6W3{qr2ooHMnz2G%ONgjBGI-HDcvF|vX*fgCjgI)5mxu;H(vdnMf5+%fF`%GB1;?zXdo?d_y`yP5j!G41$3qU8W1tVw*oXijzFvT zFb6CG#Ev+>_~I-}Nt&p^WC?ujgK1*4wiiie8LcuVcKS*0N7c?vdoUx6d zCXBNv_}V-Il*PQz!O4gU9t<_=DLl_8HQ945#Vf;t3m@8YKNbFrJqEL-w{t=fb2>>=k#E3syu`W6?g>Xbp3o-n%#GTolX1qjG z`;Ba($h-nW|H%!J8#&D=#TNVsV5>f${0O>1iXZY4cZ{5wOjO06`U zxhuyJK{RK?ySy{9nV_f%dq)C1NWthqQ6nb9yGP5+$No>uE+cHb+W1H0Kr@s8vY+fF zn=_nDEK6U|k!SQYn2AVh+aYF5KxYKQ)X2!2*+|KBOOs^I=5)^IgwE)c&T>P^4q>}> zD-xH~Aq?mw99#*($|jnOJ}9&g9-^$nFb7sS2_6#4T&#%nj61Wr2RmuR@DrWYkwk~M z09>$399*)JYR_YgD+~JvaDqf#0L7<>%=*L1G^$KvVb8FEPe}sHdjP?3WKZw3hd*+f z1mKw$gUn0*yGdddNBp!mHXPmQ?DVHovyn(S%60(uG#HYfn zLHZd}<;=F}RMbUf)JApGM}<_3LCGMB3Q@vN#4r(#oPk^@hkr{S`qBj(TOhk*%l{(L z`N)7uGRbAcI#)z93G(`-! z1uf%_W?QCEahP!Rg=?h=x3~pu9mSjeOc3&!xK#o3P6aTs>p zy2xpZr%F|Ez_0ldSW#urk{F51Qix6r5!(}&E{Z(7D^iCMz%3iTC3UCsB&-J=M~qO? zcxhLbVTopwnUQ!$IK7?Gq{q^<#~?JdFugVeQnfeoF&I$QgB_$-#n^on)>ci24GF3w zni>WxA6cu}soKpB{D-GSy$vy#x!M&VQdI-H*Jx;3kJ8xOBECc2*5!=UxRu+vrQ5o- z+q=cvyj>83@m5*QC%v7Jl3UQXO;o@&+`~oO#8uqIW!%Pf+{Y!Tue-YkEnLVokf7qX zeYM=p<=oEo+|LEw&=uX$1>F876)KYY*8d`1{!mFWqTI?|-Po1g*`?juwcXpr-Q1-R z-AqS^pg!GDS-90*8Y!xnMO4@w-sDx@8QNUPYaVbif4`3^7N;2ySR0 zA1{xz}G!S03Sv%hDW#Jpf;T+cC9p>R4#*mT&8Z~Xpzct?w z{?8gFVINlFC1&C#cK+fgX5my_Rj4h8T*%B3wPE!QS?+)ah2=;HhTM&v|R zURDQoFb8dR5AytnX9nkRW)OFd z=4Q6$dba0#{s!d|IEQS8U~ayU4N!z_t58cr~)uv zPI-n8X=V=)5Qb?$f`)zxi+&G`UJn!i26Db=ddBCJR_T={<3<=}bp8mauZVy3F2W>!teoj=7UT2gp2tYUoqXz1NQ0n#&>5o?CcV_9V z*6OV$V-c|FW`Jqoo#_klX`Dvrg1+U0KIw$s+_H}9-VU^5&a>Ng(hU&9k zkGnoO{P61+G$*Hgzy7qcxiSBgCRJH;tuX^ z=xsNV?2S+aus-K*Z~`3)qq07M+WzLhNblgLh9mHZ4an`_o`xpyi25F8asUJH=7`7U z2&cy7h=6bTR%;Xp26hPU@9qcyXNUXF@BKdK{$`+#FlYx4?rUi8f>>~MNbrr=?(UZG zj(BDX2#}ie3~NtR&R9>b9fnN7T@vm zo|+TSa3pYqe~9wG>g5y}X1e|d<&JOy?+6bcW)M$=e<*{hO>+F^h-cmaN7&~sxNQud zZZ|h)>?Uz3;0QiH=M?|*V0dXkC-k4X^cFwo=DzWSAnE0v21J*DVxaV4E^!IC^4)fG zL3rvaFNi<5Whm%~MNsCAF!M0~0>Qxbn|70pScHG5h9FmOc3^MQCUe(@_Gp)Okn947 zAnIzUY1__rbtpzxk7)_;>07AnVtl`C@bBBMZEn}?b@+C0SL|~(Zfm%4rT(yPbk=P$ z@PUu|gKmd+bMS(Q*Z^V(Zhl{fDDVg$N9UKu_a#s3+kSU`&jRor)i;o16?{Z%!hWAVObJqBc;BtGf^f=}4 zTegLLCisXa2#RlcFAsR6Ab5jMbaglbf#-K?$bp%!`GPO`b)ao{e`!aTfPG)?V*Gay zczE4zcg^q!upefKFNllBdYfnXb*KgD)&`fRYo);EI`8pw==gj0fuUddiuZDthIa^G z<}dk(cSmOk&+dX4XMvykcK-*!*LijT1|Tmq36OEbFUD#B?~qsb{yabTjTnS|*7^kR-4Nz?F zH~Y?qY12N{X@~##m;Z6wYl1+3b)bT5|8@`v1}vBWm;eYz>a-=1AOXZNb?mAQF$n0K zx-JF}2E#I8qCkQMH$+nCuO#6 zV(4S_--cp8KY~P&(Ard*HeWtm=TxuPBUc-#OKI>Q2w8u}f`uFDAH$eABTH!e7Higt zF`2G2M`kTzpX{1!tr=KzPIg!h_xu_3sX}e9AF3O-ZncCE39C)yYOWfRjUD&uEjbaY z)RlBK%hdW0`BacqWil??G4emo&8iVGcygVUmm+E!|E|&?%XL*scl|oi1j_IRQQX3O zIHq$*Uoyyqx-N=L0Xh*kW5THgTRY8sagjkFRK%hL85QKvITd0R*NOfI`Lv+~9e&uNPa-;ZV@K>1D1k_Y zNg>l8(+FmeZVbWPMefs$)pn(cHD4~TKdMKiaD!M46jXL@$q>)NGDW#QKdMT!vYPu<> zoqGBysG*8FDygNKdMc`^s=6wxt!}DL7GVieBN7O~YNv$XElHDFVG(8xf>)V@(ym1P zm#2TmrKJUPQ1K>VZ{%TR?2=*O7@=ek`oNrm>%7LqHOP{Eyc=!z8vrJ(r!(*-B4jEPzugsi$UK!&YTjL2E^{1YWNt!AaH}W1|EQ zp;azT|HL~Ucwq@NZZRBZhPh@-yLvk6sjI#^>#e)~I_$B_K0EET+kQLlx$C|=@4YYD zD_nBr3oowaI?b!lG6gcwjDGVP{;LFy{DXK{BM^I~U(CU0vam&yWaa03LIvzUa2v1i zQcW2okeo5g>zu9QJ}p0WRbv?JRdOi>6GkHH%Ur*TCZ+o@;w*QH5tzu86N$O>(l6p8O;zLn+EplCqSh zJS8eqiMpB$aVTy|*sZd%fj<1=7v@mMKipWHIt6edv#jMUAC$#^Fwja|F%E4Wq)cU+ zk%h;6iSYK+$2HaxXIcGX|iN4Gna*Nimld@GGgTN8U=FS9uG51TG)~u z50YOD@k2ZbFpmB?Vk}*=PRJ{f3bS(Tx(S>ZdPQZrbeS7+7XExmOIs>4jSdBgt*Bbg zfjJba)4Pee^mndf<|zfW_yse9=@&ox4?ir8$uiA|MUW(t35y`61mdE|jr4)1=J2VM zKq9v!crZsEwT-5p(dix4hNHUz|xEXu45P1EuBzj|x$kfIt|^wCN=u_!8KL zlpVhHtx95of*`5GNhpjiH7zQWJ{E^Ld&Q?huG?EL$Yi_9^zJ{McTFbf$)Oe%%3kF) z(1ilNp+KRV~A#S@C*Y-M8gUcsr96P#4#GUMx>hs7yVtukvHOL^WH zA^%{!6n#u2UrKR13D3iA@r7PkrjA0E+fWloG&xqi|o0F(Z0wWHxm9Z7r zErLW6|0rRLoV(y7F)hBLy)&Nktmi%RxzB$7GoS-4XgqUH3g02kX%8bHEW7}$3Ujm< z<2KJlzW|M45n7b@f?STmEVoB&o1TnC-%W=_ib`3omuN8(`8YC90RD9H!h$CdsM;g& z{uHOI)B&vWHgul%ZDYRBn>l$vDlsnVavXwQQkb#NM_1}`N z_8!TM1O??wuuM>p{P4Zbsq(1d5gLz1$hCoSvwE@t9W>B`F7%-jz34_iI?|J_bWiq- zF!CGf2!^X-NVA*dPKkP?pC_3{n#+9OIcbPH{pxJduRpyqAAy_rUyg3TCq`-hbcZVA8V#>J6u^juPGb#2G!#{eSn-+tRQ@rEIToxyQQ? zg`dT1jt184j|=<8Vbbn_DFzEdQc1~Lhi!DTA2L0VQrRvn0 zNFBIR^KNWH`~w!hVaO*O&r&=rMdDC1^K@G!o=5<#FsKaF`Ft-}ZX((|wX{qw*7{{KG!2A}|X5>Z%}B1v3? zNdQvl5?gtn`V7=j)KC>28EI&SK5Y)iXvV~VQ$6vLf8k%NIg}479=ctH{0Ri6tr-ypR0B>{v+)U6Ou<#01QF&Q zbD<9*#nNHWM$N^H6&29j)Cmy1#MNlr3Z9XV%~x0~pjWwxX6WGx@!{QJ#vfA89zjJ6 zzQhGqixXyFGhvl4y&nsOjE5Y<+bz!+^c8!s;jjeU4Q4?iwUYa6niitq0LG#$&LS<= zqAlJcF6JW8zTwsSU-bWxbLy3LC@wlUJ z2G^uks?*E_3Iv0JZR9`9TaZmdpmJVdIfAie7f2`qr9KEbe8K0vL<$Upg_f(EOrMk_$V z1+hl!vErq&GAoGGgj2j8xB$Ydrjt-2sEBSWtcI(Dk_$D-1WA$vsVamRfW{K4>XE60 zx(1oAj$qCGQ^1zkV_M6%Dul5Hi?ZE>VQNiGe$UkFd!B?V8e32TEccLV(VD7#P7-R}VG4EiE#XoJ z-~LJ7vd-Qz9^?A$E#9r5H14Q49o|}Q-EJ=DcCP1sF6f4?=rYRk{A8eDlB&eT9{P#q zf|7y^li+?~W-jgCV(Bv0>9+2th>2~^?(dGS@-8p)Hm~zO zFZA}GHdZe2lFCUWUY}HNC+#hL`ALK!E~nTo>-z2W5{mY+O8NS3Y)}4=S&+f$ye>Z}!Ts{+dm2r&`2D-Huxs>s-fUGM}u zu@-MJ7k9B2e=!(`u^5jr8JDpcpD`Mzu^O*28@I6=zcC!gu^i7a9oMlP-!UHNu^#U+ zANR2z|1ls3vLFvKAs4bCA2K2*vLY`sBR8@mKQbgovLsJ3C0DW~Uos|VvLN4v=d(WVGe7sUKmRj82ed#BG(i`% zK_4_iC$vH@G($JELq9Y`N3=vwG(}gmMPD>VXS7CdG)H%|M}IU(hqOqKG)b4VNuM-I zr?g71G)uR%OTRQs$Fxk(G)>pEP2V(5=d@1mG*9=mPyaMf2enWSHBlF}Q6Du@C$&;9 zHB&dWQ$ICSN3~Q>HC0!&RbMq$XSG&uHCK1FSAR8FhqYLbHCdOnS)Vmpr?pzIHCwl} zTfhD_T*tLs&oy1wwO!veUgx!5?=@fdwO{`=Un;JwqYMOVkfp@FE(R0wqrjw zWJk7SPc~&&wq;*7W@ol$Z#HLlwr77fXot3Fk2YzSwrQU>YNxhpuQqG9wrjsOY{#~2 z&o*t>wr$@wZs)dc?>2Auwr~G7a0j<=4>xfaw{aggawoTPFE?{Hw{t%?bVs*zPd9Z} zw{>4Pc4xPCZ#Q>$w|9Rxc!#%mk2iUjw|Sp8dZ)K~uQz+Qw|l=ge8;zZ&o_P7w|(C? zew**0VsL)*C3b$|zWfhV|vWA%YIcor`>ghx0}JGg^OxP@PMQ2tXm zg=08}ceqV!xQ2VUh>!S6gE)wjIEtq@NSnBctGJ85xJ9%0iNiRJ*Z4xqc#GS(j_-It z6XL)md!xxk}n9n(>_`xqg!#L!5G{m_PQ}L>x04?Bo zo_|9x94Ph)_@9t~n3H+@nt7|Bd74MMq^tQ94=SHP`kJ%3rCW-k=eehYgEY{|p&xP5 zb-JgI1EGt0rQ2Vip}?JcIyCfxs(&o21Kl4y!>Nx0IP`+<(s~Ye5+ry!{(DIiu8+B& z(>kEs`Z!R76!$ruKf9--R<7qcqnGfppGsb5U?vy14?0fW9HuA`HZ zvQPUrknq0OI-l72G&H*mr%tfv`84$UKRkk`za=JZ1T7#uptuM)e3;+1FmQO8Tozj&;IAn|EBCbHYEO@?3|!S!)k2C zq6;xVj2jao*f?xSBqaQ&Xxz9f2{+J!)-W0t3IG0)P$vEm1u%jCB%lZ;jv^L}2~kP_ z^vqzBlnOEa10j(lIB8BW{$bg$VYes}HuM2Ta@LTgI1h*IY`%_p^T1u<+K zwHza<4Y?SCma}pHKBw#q9CXUj!V9}Pfh8}hx+*!--0G^i{$M<-wbwAx%Aw7+ zv#Bn-c=PQ;zY>BZ!R$gBsU-aJdoji(#}YCdkFsgPD-p*5W22=|ipax?K&tE?_^$XT zwkUe@DJd+q8!9UCiX@6lBwbvN$vNq))6P5b%u~-j`RvorKLHI?&_M|;)X+l_O;piE z8Ew?jMl1cC%6f@drnohH02>AcM(Kwb+_6 zF1f%x%m#2|+LuQe@hOuyIK% z5)YQy;GxF}1LXv%d2o=1;#G*DraQh`K>_!JjM0W;vN24V{tdRF0FSEJ(`$j{iB?+^ z*6iMp7j@DWma_0_r6{jlFd2gWGec@4${%`o7mUnQMk$n7zF5IpMaT7UUn@;r_0?H# z-SyXDk6rfJX|LV(+i}lb_uYA)>^ku4 z(i(0!0Rt%b*R{wew?}cSEhoW)T-I;|4Y1%_T1W^B;2;+)yubx{&`TQ-qOQOIWi4!Y zkOuV79>~zEUXD4PruI}a{0ZrL7}-^(I)=7?oFIB-14jHKJY>}kNEx)4s8@K9@geTK~W)+z&J8Q$?zYG&|&gaC`1Vs(KRpIh=h*N zf=hv8cp6aOLAuruDEc4;m&y(E-X?*wVQK;gv&DV%lPkM03VOBi!PDZGkoidnk(GH( zLP$3a-KcLV>TBE%cfvOl{m*h=zyMG#6)zEViW;e^jRdynLoaG8ZncErq39-)f<(!N z4H+UE4L8c+`Je?>8^<+5C;=h;Q3BjkUP8XN8Eo0IQBGpQGKUZo4UIuM5Q)(6P+6XK zS<+!V`9dKrN2KEU(g_nt0WGfJOz-vTUW@GAJ@J`OeeRQ={q*NQ0UA(&4wRq;m6ZA# zGQfS2#3UG5<^DecRGAvOazdQ(8R;7OkB5o@WfM*3{cP#R4c#&(82E~i>PM{hQN^U? z(Pr62%@M)w)Vbrt_rmfVKhlH= zU>$X`UnS^nCQZrKT#`;uO|PO7Fyn<3%h835BO2-X2WG}6fm14^GR~8Lh8p{iMmBCn z3IS|L{v!p}tpbyM0XvFfClpz`{HJk28mYc~b=;mZ%9^#R;SQsBg>Fg!qbP~3NDZgN zSE6LR>QgJMB4=FCCeonwweNlLn_vCzm%sh>?|%Ut;3a{FCz`}ag^U)Hu@0d!xxtSa zxw6hbBKT-}{YP#ZoXv!w=NlkGZieR@6AnAWssu$B9TFC2OI9JSRW=(* z;2v2|8=uaCHpGhhDMnTxm%GJeT~XKo9oV9qEUl$2B4ig(it2_eWZ-lxw5kj1>8Gjj zq{E?M$gqxL#TrZ0l)`BUs=DF`YyG6SZ`JW^{*hpjPRFl`ShH8eoCGrOR$DvnG0EQk zn4oVoG{Z-+GbvM2A+9tyB|>_sp$nGB`k_P+k;W`a_{FBM_!V(;tEpE4r(*o|p~W7$ zmL)^e$RlEsBoA5UeIW6TTP)+z6+%-;YQYO=5Cuz~ya+?ax92c!`p<|e7Eip9(f^|trD@ttpd@0;KKc3nh##J5nx2g9CQ3VuxVs>-Bo;K3^R=j>uuj7+0FomDIz zx4nTz5al1=AZ&$O<37$nr6C^Iamj>)aW@;SG91mZP7}zS?I!BS|0uFpg3SJ|pQhx< z-Ewlf$_1_n-pk5YK3rCZl&QK6DJJ719dg0|BvZ}!B9-P!o0SM=n>iSe)eQ@>n4aLL zgCpwMLRzD?%$}H(?0x^B`ai7xlkr+t&g6|Wdbo~BONTs5z}n+yr@8G!i54R**sA*O zX3Ub4)&L_*I}sKl|cmp$fZ_q3ohDkjT*MG$Ll zIex%KZEUi}ViX6ww&k5DYyP7V21x>yG&A17QO?|sx68xQ^-=4pTtKb)lI^#vG=EQ@ z`qj7o^|7CQ?Qfs^jyl0cYA7UdK!g0{hlVDZ^^&pr0Jo_Xzxm6Le*S~&XC^-4Rnodb zrR&M*AA=wX>31*1!UT{gu8`mgtF&|UJx1%0wZKFXcS2L4n;b)ss><)?Q$r%Vgd)}uG;F3UmgPQe9-RRhVP7HVz?+Y zcr5n}<>HcHEJ7~D3UCKG3uRWVHNIg8A7YfQKqr=>Lc#!gNMxO$2z}yob&TT4?kQCVhA3A=9=X|Wb<@fLA07j z&giQte1n(#%8YCabx`U-rU%8+P>CKbAruY9uB4Y9?Ift=AQ&ytbPNLNYg;G~Bi5w~ z#G=SHps3bB{V4F~)X#&ADqD)K1g}aK^F**NrJ>*o-gu4{NWwlCVi3hb#xTUJbWpnB zEbqVy-jFXDHD$0UX)+YiP{>Fupzt8E$14<4`A}l_^iDzW?mke0AvOz;Kt@1{=pVug z-YBxq5M@_r#AH?qwJ1c#+>rK?h?dYJ3>YF_YK0p9{IB?=Cm|XMO&ZAx7-B-C$s${k zCgJ4tUL&X4&?CP>Ar-5YQlgPuuG*MMK$tPkq(mlXlG_qY!i)f5K%c)6zK8@E4= zkd&oGK1z{lq#VQIu?)Ztqo{;!9II1c4iPn_*bb%U zf@%#C^Bt2+0`5RAlqwyXfv{N-_&+ z1LmA5ghCN@&LjfM!ZrnEl;(;$R?3M4;zM!lt=a?LW|D zox~tQjwn|cv@Z=bP;#j?$|lA5CN?)jNM`dtDl_Gh32r75E!C5|*b^g2r_~;mNE$OP zA@ulo@<*{`kbH|K-3)>F@;wdnNue}KrF2TEv`Ve?PL#nLa$yEypbvzBB&Jibpy-r> zA{P*XS@dI_qTv~e&_%~|)XX$9PeKH6!7uQp+oS}r-s2m3;THIS4{8Az!sH)|1S4`w zMXAw7&Iw^oE^^}POTb`%`sdCps(Xm2AP)X9f%p+oYHkCNYUn5kUM?tu+5ihOU<2&Y zgoJa1SilZ|qy>;nRY`C;t4aWx(@F1yj7X=I@(0^cfEJwQ1abj=LZVabV+GHrNG=mX zuBd=86=I?>A^gumu)qg`f{H{0QZ?*Xp8%GIRg#Q#LhVUY86`f~Za%|Ff0Xt9GIJ!^ z6VvWaW}e9raR!ilh%>NYxBMn1ljQcO@=&I=DEzb!Y9V@VL{TSzWg4%0Qqq@z*Xw*od+Oh=7`cB*v0Z z*Q!o8EJAn*j26i`2_*nMvy$RVO#aKPOoLHlmuD{3&GRs|?B3}L=oCmYCQ9mwXRows zt@diMHfyzZYwa!2;3L^^5>}vSr44NG zss16T&LwSTl~x@90B%)K$Y=#k;`_u_$KIn=25rNL=4L7f)2@bbp*8P-Ld{y`Oa4f8 z6Xfcip=VxKP97J~EJGO4^F! zFG0W|Ob`@n1ov82Fw)W&{yx&{vBXBbpm$ezA`IfoMb`_E;3ILjt$2-%5k-tPY6xsv zW~QFbRk9`}25DB3B3WkYh19abcn@ccRBiPJ#nv}-8aHXtWQ+91ArORMbkKphc7#c| zgiZK_Q8dl|iuL0ORXC$fGVWTH13LPsXxA3{JFCZgkL*e>Ry zK~xAO)W;^8fZfLa6F-haDvsh63AvdNd4`WRWHuE(`&d3LgXcN}ITl%iH{*%1Fpq45 zAU|ZN79@(FU^KoY8bD+w$JnQec#x$5iDQC_Zz7D_1UW$FlwEiWFNN92OJ)ahB0ggn zdv6>-my~;O>xFDubYmc= zms)jr`Yb3~7h9lzEW~MKx}>{0t<`$1*}ARW`mG%r@+6ontlGY+45Ll30cL=!;hIst z+AP8v7ojFW!P*o1`mhl@u@!r<8N0FTO-qOZn*kf11?q6JdN~_F03Kkk9lKEex+?fbs*JHPdNzxi9A9%COOpdVal5AY$r|6vBY zo4)&d!5O^49sI!|Ji;YhWYS?Du&lk`8zYok!6&@KJ^aH#Jj6wO#Jl=%W&j^B!UAr= z9ypxDUHrviJjP{w#%X*P568W8!5q|qy=%P3ef-CPJjjK7$OGzLZXq3ffxTOt$eFy! zo&3q6Jj$iqIV^h0t^CTdJj=Cw%RPLmxctk(Jj}&>%*i~rv8v3`Jk8a7&Dp%o9~uSS zJkI5O&gs0)?K~GL`_A=z&-uL1{rt}XJ?vuK|Rz(ebh<4)J^@=Q9adFebrgL)m{D7VLjGmeb#Bc z)@}XPaXr^{eb;%t*M0rhfj!uTeb|Y;*p27Y-Vy%cAwJn_8;w}E-F+Srpe&Y{* z;6481_uSk;e&k8MeVLs+%e&%Vu=57AwaX#n%b$;i0zUO`Z z=Yc-xg?{LXzUYnq=#f6@m44}&zUiI*>7hRArGDzEzUr<1>ajlSwSMclzU#gI>%l(k z#eVF`zUozxa*+_>n*P zm4Eq}zxkd2`Jq4hrGNUVzxu8J`msOzwSW7$zx%!a`@ui_#ee?%$-n%~|NPNE{ndZ{ z*}whW|NY@V{^fuE>A(K%|Nikm|Mh?W`M>}D|NjBvpTL0x3mQC#FrmVQ3>!Lp2r;6> zi4-eZyofQQ#*G|1di)47q{xvZOPV~1GNsCuEL*yK2{We5nKWzKyoocX&Ye7a`uqtr zsL-KAiyA$OG^x_1Oq)7=3N@kh z3pcLZxpeE=y^A-m-o1SL`uz(yu;9Uj3mZO+II-fzj2kY z<;ATfdGyyY}teyLT5ic@m%=$2B0X71N#>LvRq2mFNSav|h$yaEVnJwviQ+&| zl1b%^S9-~3pML%cXrPeMDPoyChNMZI|Ey`z{sxMKG7l0b#R-y|g(fK{p^DlWrIa4& zDA1gH3QB6Jrk;vwaWm-ROslTG>MAt17h)JG zNr#4HDTZ+Nn5m`*-RY@D)ry*Gw%TsXZMUP%@QgFN21UkPfT&ef3cc^0~&A4 zF~_(#!^ge;79>eJ=}h6!$V!25Ogdvcr1C}nph3_Eme7kV6SlHcZA1WvD6mKkchsyu z&*H042%-eJsK~=w^e~E}T1<7-R$q<&^;P0_%r(avID?DRAkcD-$tG*lgg_;qOi;Bk zTVzZ+>oAcrv>E||PC94|Q8&^V&0Nq1q0Fey6g#if^S?L;9n#Po6@B#62PqA#nuix; zH%3ugnRVx$e-3)+H>ulo=>n)LGTvsdj`BZiw_YZ#eq$8(+7{dh`nv3% zPYwJalr+9tz}=Rd?#RD16X?Z1$c*{&e~)QIs2LJ7hdE>0kb1V89ik7-0t zb|eDCIrb5vbztEi2}F^-I`sa$e?-b|#d;u(Y~%zEbx>gfxs(1n)IbwrD1u1fk*FjP z0%P>!AO6_Fj!<9>3}Q$h>-a+A9!0Npq!1JWxzyzlLOFsUDIq1K7ZJli1Pbz~iZogW z7Nvx(9r?i+GJKLeu#v*cHSA!9!kpvS7(4RGagKDX<8{n;K8~gDUWqVS84~ik7iO+) zgQT6@{I`$-0`iczE6D)|!igEpLuVw~z$9>HFhRmF6a&%I3NiFS4ux=pMGC>-HYE@< zs1Qt?s^leS)XAfX>JR*oB8fs!w91L;P$yVglghxvSX!?jQ5?t=4RV50f~guqAX73~ z$U|oC=!PWfB|(^UH2y+P&u#1)Nb@uqr(M}`oa8L0Ie&x4JZfx@?CN7Wwy?;PS6d()prP3A=(uqm9=>mf80ecYoUk!wgXEq4$^VA4rwNCJ=K+C;4mq^>%mCWi!3%b~t$n>{1gy|k&uLv{0< zjBTuAA6uGq{;HFmy9z|!*6{>{tgRPj6UaK!CJ;sF(_|-IhZQ*F(RD<@cgGNj7C7{~ zKT^sV|CpPH`uGoNj8<+|cwyXDSKHIV_P49kf=35pvXG8c0wq|f8Wbu4L8Rj!uLS{Z zfvW}|c(X+LyXH*=(#shQiKn${R4EAu{HSA%vILKu! z7P2HRv58N-7|G_5vbFlB*{CtWfvgR9L_k4n|KURk{Gn_`e1Qub0aLpQB5zs{$c{~> z2C`qG5O@`J zQ|mo6DJ?W3HO$}zDo6n>LLpTYkV}UsqDL>vRl}+FH$;hEbigQSVE}JxMD~>=7BB$B z9foM99I}M0{UHx?Tu8#^ttk{U3|DWCAg~0{hdP99RjG1l+1YENv!M-DXaSz zX5a!9^dT7h$nnlP+%A#vnZt@5F^b;}?|92QE-F4Bi(6cb)iIMGYs;>Yy}VO9S3&-e zjWAh}RhkYp=#LTqFh@e8d`J&*w|}>tZW^@vj}VkCpd8A;l-`_|M2$5_ojwkc45vjfq1j@Fj(N z^fQ5wvcXK9DTD)-Y61@$N`f@H6F2c|G^9`ien=tJ?b@M8hKWcesAa&?sN+F9YnTL4 zur1r>>uYK|ya5xaKrAs~f2eXGd~dH^Lschfe{yEF;$~sl{j|rY8<$x8*O7`OI^r z-ljvH)BIpj4Wc|9Ocsd76MFibcc{7PID`IZtBo4Q4Gia4N1O?|)16Cz;{FhaIYB56 z+gkkM9A0MR>&!$VHA{TuEf>MeC1BZfWZ@r-xF4@rQCqrocjwmx6B>>H&PPA}mQHuG z1bj6kvy!Oo)n=)gqAtQzhmc`z6y@Tw&LOXZl9;~45U;^rFVK~CGxBptg){1R2x+o* zI)Z+Lrbu>YOl*Q-1;J}Yf>s#91-{dN0+D+IkyB+gC1~;wiYH#e!Yd*cN5+N_9yWQ6 zM|YYBgE1(BGx!sn*ERRnO@0tjre!~D!&(W|W=SSUzmsJWz;LRiQtO}tEu#g$^I8(u zBfA%I5|9WdRb&G}aaOiE>Ht%8Q#*$TPRgfpGba!TWm~BwP-}Bi{;R`C+|@?i*CZ_k zPMvT~>enM6(q1*Rc=I5D9&#d2HxQ$gY7Lk@{-8m_LLyX$e-h9M>1PnlLP`J_CQJrY zj0S5Q!hTl5A#|8mN7W(Kqk`9iY)wND7D5p8ut3oAUQ)v$n3g@L=r`{bUx-&Z(w2Zs zGG@?%f_P#*;z1l>iTG6z zQbmw+H%$f;iu|`=1i@ZXw}=9<$=ESC7?dm5Qi1e?d!tfrCP=KOa1sz(t`%Jgrw9oZjvX?NR``1n2YUjg zj$SA?PymIz@{aLndSz1vs?$0Gv_QYumAErhG-FAaG!JOUA=q<~F!>1|nIu$2VJHb{ zOa*^K^;|kqksacf29}2$f{-C{Qw%s)?Pn0G1cA9?LnQe`lQW7VfA)!9G>!%rmUB~&v8O-B$w-UfM`<}F0@N>K$a4G^ za#tpY(MJYANqGfyK!(O`j@f^N5}5?4Jq6^D7C3AOQDKeQm;33F&?A}ibtQ~>nF^tq z8OE6->41d1bwXCIKb$5B_8@0c^HX6=+%OkT*H@4=$DtVvt1=fKMif ze10}NkF<(3qAMGKpurX|gG#98AWeOmB1wWg4EYZM`f-3TIK<`;c4&ZDS4n|dsF^BF zu~tP$x;gC zPMsw;pfzpnvnIy{Wr%g9SD2-?^>SuYos2|tD+OFn7=0&2jTQJi<62Gh%7&?kh~M-` z7dSJ}1v$b)p_F+LcsP@R_$w-@{)-%<0g8a2{?Kv_d4CpJp?=d<45~#P=???;nMI>* z2kMy*xHMrkC7>Fz06QfNOQQb(szNfC1OcoCAq9e zi?m5gHMsyQO$#f05Uf(N0kqHz`lJrqP@JO&JJSeVD1Z!R!wzA9af0+)y7F?k+M?_F zos<*^?PCo|z)<=F0+7%?>%dPPB3dW)T+F8B+`aF z9u#>yvUpra5a`FK@W)gjhMx-Su&<^gjZ2wuw4j};MSyy;7MduWOCpQ6swg{QaFTHd zTCy@bphH)B*fS7PRf@Cz>q7q^g4%`rvKf*_GX@`lG7tj1bMz^Y9Kp}@_{!Y%B=FZ>jbBoO-Nf2qnO^&kyk zFav!64CgSR9MV(4CU-LlVHlZe9kdQQtN|)Ou?)I?Lo5&Ha1T-P532euIlRL?{KK5e zIVlRV7$Oh7-~|3DPz%PAAzAmBA(9S)cA{y5U<=`w|G9c5I&{xAXiY?^H(3zZcE>zK zslD4(7{U%%Fat~6vySMGB&?zcyM4mD!Y~ZUku1rRToW=Jrxn`;V&$t%LUg`Tka|^V zM!ZTM87w4Xs!-)Z1dEw1q&$^zDy#1 zg^<0REZ5t}i>$(p?8vxG$R-q(l3oDgBsI>Kwh zC(MkDZ2oK?Hq8$W(Ge}t4^hoktSHjPcVHs2-R8|w;GvZkB&4Le6E+XU$s?YyDZBd* z89iYFW`gg$CsL9Kn@nMSXfu}?kp8*47TT)&%pv*$hhYN1JOb2`8nQzj$gCK;8V1Jp z1j|Un%q+^##*4xeZPize)ze(jXeAH>CJ;bWMm2IHbQuE0@Kp6627KediF$RwLP{Ip z2gZUkDAY5M3M4J`S;GR>Rz%ln3DXHJ0giG({+kXGv@Ad*M8IZ55|C9+q|YMa$YWGS z`EZC)D@c+}FO;3Nof*6m+XrOC*VEwC)*G;)$cv`^MkJiotIgW2?b;Y|uw8PjuT9&v z{%za0%@8bGI9~F|xb54&4crr5YXS4YTQb|hjoitt+{&wQbc#$B`$t^D%*##P)otA| ziaqdhYRs_7au_33ecjg^=;qxJx=iL zzaA{z(~aN%4d4OZIdePMC{hntYnLx_+yc(v4esC%4&f0l;S)~b6>i}dj^P=u;Tz83 z9q!>D4&os$;v-JtC2ry;j^Zh<;w#SLE$-qk4&yN{<1y6MQ-Frj^s(MUGC-nUk>JB zF6LuS=4Ec?XO8A+uI6jb=56lgZw}{iF6VPj=XGx9caG>qq z0flbphmPon9_WkC=#4HGgFfg|tq>M~0hMm)myYR~jscF&>7DNBP+{p70O^u`5E-BW zE`aJUpz5p6>aEV|pAPG>F6%yl>Mo!G8Gr$z9_b5h5FYmc0)V4kGwZ`n?8Ocf0`LG5 zVCuN8>!dyq!Ok_tF74A!?I`g87;x*&&g;((?HW_<-wy8K&Jhye0vVv~>P8UV?f~M> z?(OdG4)FlDjsd%l?gG*6?~d>JuI~^~?&t2%(5~+RFYw|{?&eMbk{0{p{|{K;R8#m^(hul&ys{g}u6JL3G&Z~fPAV$-i9)sOw% z@BQVZ{UpNu-%tMKFFxVlA>(iU>(BmKgZ><%{_QXS^Di;)F9GpS|M{=~r(*y3zyJRa z5dQ=YBv{bkL4*kvE@ary;X{ZKB~GMR(c(pn88vR?*wN!hkRe5mBw5nrNt7v7u4K7T z#)}LVC`h1L)8@^Z4s`D1>7eCLph1NWC0f+zQKU(gE@j%(=~JjtrB0<<)#_EOS+#EE z+STh z6)*0~)^X2@W@W0SEaCA^w{tbiW!%~GXV9TVk0xE(^l8+oRj+2<+VyLXmoG<#Ik|FL z**+(3=KU7-Z{Wd&4<}yS_;KXPl`m)B+_}`;j%lC0jazo+-kP6p=ic4>cktoGk0)Q= z{CV_%qqn6f8Ey5o*LP>vp5Fd_{Q33o=ilG|e*gm%P^I-CYp=HV;G-!w?C7IwKnNq0 za6$?zwD3X~0GtF-b;EVI;dOD?-KXi42BaxX_6 zA8W8jFVj?WO*Y$f^G!J8Bn`|a$+V2bh@|vUPCone^G`qn6?9NS9isEb5JiNtL_Mc0 z^ifD7m2^@{E44JgL&ZeU&PDTV^iohm6?IfnOEvXWvNDxuOfox#s8L5zm33BHYqj-O zTys@5B35&B^&wbu6?Rx+i#7IGWQp7rqFxon^VLt2m3CTctF`u8Y?WK~p=O_a7T9RB z6?a^6%Qg30bhp~pp>E;()uCY1m3Llx>$Ue@d>2}`p?0f#x1o6R6?kBR3pV)RQ1@l1 z-`oCWC}4ymmUv={E4DaDg3tLa?Z22W zi2C5G-yZqImH%6M?2nC`p#Hhz%i7beqhx&X?wD7;`r*SDzi;xdY+P~p=a0yBXh5TO z>L7)Z=5{TMy0}B%1 z01AKsxe@+Df~O-$>Q>jfag=d+kQfIyoB%-%DB=s8+g%=i=e+%SF^e^<-xq&qMEm_o zj95HTBqjO1!r^d-{{G|S4v|O}EZ)$Q%gY-LPk250S@M*^QldbZSSIO22~F<1qzt`d zMNi&Og;T6#mV((!ONy}}WHh5d7!k-Mun}`^BnSwCp*kcGgad@+hUoT4!9QLwkj*5B z2r%c!gMbj7BsAswD0xLm4ilMz;v)Ezc~4W4a+JXQTNif-(r(nmRhvc{*;K0%71dvT(p@9^{$W zh-N`JAdH&|B&GxLqXk835Jf<48n{EqPG6Ktpn}qeWlbwtHwvd0LQ<$jZ7L=u`_6Z^ zRjXt1(fBT#$)Yds-sBHGgJZ2ml2* zj&D2=ue{SBA;Vb^=dy0F0x2vYUsuxyC}M!pV608o$xbR#ma3~vEiwVhr_7eNw@d9T z3!C>_ta^{QuceA+E$do$rdFv_vad&@N?%jOc4NMBBHqNxzWYu!e(E(Uf7i=f2V?cW zM-u)nKp$`tR6csL~rZa?bz3}8RatY z5b9PhwpEAiE$%ouK+DUJdfNZ?Lo=F14#kbv+O_E0CrsMw^?n(N}>u*3ab4qW)1E z9oIyocc1xdUa4Ew-)B>_$(x=^5XpRL)q;_&`2;SvBh_gqxvRj}7B!VgjpZzl`@XDQ zHKQ+VBzgMx-4r?O2|jmMlcH20=A`vMW}OWt2qXm86axeYe$Ij1Ss{t7X|iDqBw@bS z+2sv4qA@#dYG3)@mpt^dw{32y(9o>l9y6oe3tE(q3f-Jqw?Nw+YEnbi)bfsNk$p~P zBn^?R9{=B51wvA8B)ryaK8Q-OamYGev)2Q8=AMV3V!(DY;zqYzE)ID{%sn@E7k80w zr7H5Y()P#jb|JWn2v=z97uy_1`^aNX6k$%ZX6{fk>5@0cGLi&5E?%Z04%YCBlE)`f$#<}Ac7YghkwYN)d4={+q#PThs0W`&&!+(3;|kj z2FUq_IO;Be-~i8&9Lgz(_Jf?wNhDwxAaCeDgHX1P_`b;b25G?l0vDq$-kG3eqXlR{ zLD3lq4(Njygd7HB9RZ9SZ}@^8G_fTdhspW6e;}j*R3QBO!3N?#5?neTv_N9ph)2sW z+WV;OYp}*s6BCgv-s?Ta>#|PrGUdy><`asl>N{pbv@y&(y{kZ^n4JN-9q}7LN6J7x z8we{*JrE15HKQg!I6^55gE$)sLre${3_>Xs#1)JjL6SIwGs4=L94rVw0ZhN?dc*+& z2T6RJ=aaJN8>`-+K9dR|F#v+5utC?OL4i=IsY5*$48MOMxS86-_j8UcRJPOshX5KN zY#72U48YwX#D7q!Un{^pv#!@kGt+w=0Bj0^`vxrd3LE~+MiLxH4dWxyVXkuWFb*^$ z5K=}=+%sF`!ElPg$nm^HOdxukL|%NIFets%qlS3|qzkM=gG?6Adz}x8#t_OQYa9o@ zLc{@NL_!j_fYU$FBZ`NN$n6n5g*>qk>O`*Fz|5Hj{~N?>KsG%p9X={C-YH16X+>uN zwMC1?MyftrtD`#tKc=ujID5c#A|zi>oq@zgZfHqB3PSc9$?!NvvNN%|5`+JnMApzn zYa9cILqMfFth&NV@q@jnSUAXOv92(&4N!#7(Z+q8!E1WD5?q8|G;g!MwzvxD zM}|B^!D2~)AWf?i2oy+7RwO)4iL#k|MFg|S@xw``OPs1pzoSTg5hVKbjx0QZ4id<< z+_lfS0)&{g4Qnj2L!dnBOZQ}>4nn8T1Sbn3IBGaagX1RAL7<_;2BjM|$P7_pAvmzi zKZAHCF(4_g6ika;#1?HMfuOi0SPB$X{?YJ@${joiPrNfXOHhJPDS!i^Xeb>HTS*_3 z(OHtoPjgO)f=(}xPLv`zsay&_0tEZyFkMV6YS;k4LMJk+1#x&T(xC=BV@mL7&jy_- z$sA1iR1NyHMyVuCgtJW$?a%Ib#>CP(m+VH)`^SSwM}SKwvMYfO;>(#*HeH-h!ZfVC zY^ew}DJ%di&jV2rt<+lqA)HbO`Xo3ju+lkYQP6x*TYDUDkOHOPR8RfTDjbKDWQZL# zI?kI?zzH#Ra+ryGksMBuE{&reAxe zEGX0U^v{Ar#*)ge+A-8_bW_a!Oet|dI6`uyJ0q!|v{UYQ#&p!6Hg&T%)4qa0QU!g` zzhqTpbI?5EwZ@dl7Tr?|GFPfJPeyDbf9p(7v{ZyWl?+@go%+`LhCDCPKO0Il{ZzxiNKvH!AStI46+|*d?;nlvGR;V96+3K&$J+6Ref3w^=ud0xg=c715KW}o86kUo&^s+S zVdX(?v>*;}*Ebu&e>m2LmBIj0EV9K{y-7@}UDbXyF=7?WKZ4QJiO7RZSiKb$ZG{}} z5>%~~$SJkeqJ&$DZN&b85JpvWS)m}L!6iz8D1px@MimrUqkEh$M7;^xDL9&-!BSk) zEFYL{7{oh6nvG7I9VxCgit0kZCZNG*`XfxeO(h_mIei?dEZTNF(`$Xo*LmAEO$|Hc zMTrH;lC+?#Em7roMnYveb1b+=j2zNi#BSZjd(2S>Wxb|#QG<}%)m_!P%_|V%v%K9~ z^SzW68_OKkMPAb&%tcDW1y#f)LbQY&Ln4ay1tcF_*N`p9!+KvG9f!?@u?HaB=}M#q zy5Hoik%n@`T!O{vtJ$ud)tdrF)-9=78v|uiS(p6OVO!HI_$v*h)-Ytx-qqT|s#mHF zUT6$lg0RN7HU2JW;I7g`*X?*l1}fPBO^6fN$;R9()x1piL?fPjPw|7tXlOtY3(Wy_ zv!C>&MsGSCN7vYVd<+ z_#+Z74>D3of^(zl63a<-+SfqW)alpqwc+P@Mv~%47M2|YWF6PB&h7k%x}qkwT?jB9 zP*OG7$W0@F8{}u2hCndWD-PyKDPU+4Vn=#N-g#o~Er{5iM9IzE%yeOvK<3vu*WD4& zGV@=f{^K>^Btqv}BV`i=ao`495Y9jS9OdPjKGvB(R=Yr!;6ct%hz#OGjx%v?%{(F` zcnqN9!e)_LIP{#lN+yq3{!#mENvPabBX$i?W@1X6L5DrsNvx|ht=@tlsXrpPw|(WZ zwO1o1<@mkXi!Hc=Q@TO6rg4ada4lhBZs|usV`GlyZ{X*&b>#GSMOvG+ zqUJSp%2Pf%tg8FefgTT(?uJq106*YNZ1&_?d*W0!jfNIwVE#(-Ffk9T9c7kJ6W|5@ zDvaw2-o>T;f~$k;yX5O!zQzFs>00*W`h-ZRLjkGFqj;{Xy>@BOj*}7aI)^)fu$x5K z9Z4zuVu$9nr>o2Q1x`iK%@P#7?PZB)y1>*P#KnT3&Z$j;JZi4vye8aDTIfyW#Y|qy z=@^ymJg%ME4x4pOns#PGt$xy3d>sYu$%0Gn5i;vt^Wwuw#}(Dx%<0$dNJs2e>K#tU z5qjR~Y+FTCS5~C|BNp$?|-nyxz@@u&zwmm#$a!DXMgtoh;FQ5(ow4|X#Qu6$qsc6v&KVo zta=U4D!z7cA9r$h4rw==>GnMnD)+;fY&eA=2KrQGQ{z=4*$r<-(t!*$R1 zz}RpEgn~8ABSgKpa0hsWUwDSM8C_=XZOf2`(K3>(I0)%FMZQLebi5V)n9$q zZ++K)eb|qE*`Iy5s7T&edxDP(>f4M>90S__ec%s%;U9kDFMi`ce&kPn0 ze&~;W>7Rb;uYT*le(cYF?caXx?|$#^kC!iEncP7Jm!y(yX6MTGDa$3q4VgGBbdpGaizJCJ`E_^ug;>M37Pp-T#ZrHLT)1GMC zBJSnZuVc@yeLMH=-oJwnFMd3EugzaSkB)YF#p>nX!;de2KK=Uk@8i#}e}DJ$meZ@J z7JKgTSD=9hBAB3p3o_WCgAYO|oPUi07$E*d1Hu-eh8uF&p@$!W7@~+Hl33n^j8$l% z1ngy$VTmuo7^93c(paO7H{zJ%NGGxi+IH7KDg)@rA($0D1ovdc2tEO5XU zG;3|eUh1s1*J7Kkw%c->6|}L*IsPoMhECb7x#yyruDa`@`|Uu|u1D>u?9yAWz4zjq zZ=>!C1g~`RwxzGY0~1`Z!3X2W??3(uNUp62LmaWh6H{FAcnUKdk*NV&+_A?WgB-HR zonajAup5(wgUTzj+_K9r!yL2BGt*qN%{Swmv(7v7+_TR=10A%`Lla%J(MKblw9-p6 z-L%tBLmjo$Q&U~F)mLMkwbom6-L=8GQfy6UU5{@%Llufra@?6cEeyY08*9&6_h$(_6JzXKn<@WT^dyz$2) zpS<$RGvB=P&qJ^L?&E$Qz4g~)pS||mbKkx9--92%_~VmbzWEfI95=w{v){h^@53Lz z{PWXazy0^)pTGXHrcdYk`vWk50vsR#3uwRtA~1mpTp$BwH@)69FoF`CApKZm!3$#0 zVHDgT2RrD&4}vg+hI`=uL}8&J(L>#VcYli&}hF|9%w2ErKzOCY&M} zrzplVqA`tXT;u-!lt>viit08RsZ4X^2u3O%4v)~Hq8+PP#y|cLj)EK{`TAHnLoSYw zdIa1e33){~vITUAJRltpQc2iM@`{bLn<3xmE=9H{Z=M{;C`lPfQ(m$lo5UR;TiHT5 zmhvC5T%s&#xsg)h5FxP?$Sw)e%UXs~m#5_AFNv8-TRKvOUj$Yr1D8o;-Yu8D3}rK+ zxXfv`D4AG7>-q~c1~sAe^) z*vyDoX^Pj`9W}GpH0NX6(?>-P6`hu;tYk+UPo~~=vI~`IGdKBJoQ5{9W%cM_FB;er z)mE@=z3txMI@QB|HMms0D{o~R!o6O$aR;p*hHeu`p)%F4L~O1L0ZQBJHnp?e^dv4J zoBq)5Vo*LX>sjr_7MOsQYF)jl+4r^=jm{mfFe7VP^oq5jinVQG>5Evqa@4#4 zHg4=(tJPB)w7Tad>U`OCAX;|;VGrB77D}0-r zTrD4B>8xj7&zaNEeKdwgTIxSf_|nbZHH@i@xP31P)q~fDWIf zqYZ0GD|^kted(ur!>n^tnW+{K$b;DjT!E3$oclR6P_ZBdJmu+q$Yx>?$t+2aIu2v75 zTD6RwcgG8zarlLMb2bMboJY>-$ex_rA(k<(A${wMV;bHxr}R7(Jyuu#2N;n}clu-n zK53H=JiSIgq`~0}%aE&F-Swq7cH`X^z^)tOX5V#@>x%Rq-<)+H@Ac22u5@B+m)Tdx zaL}QiZ3oku?Hl&EkgqLx^!okEJ&(HqbFR6af3N3=7Q4{5y>OzZckgtcyVmU<{PbW>jMGbjEI zRo`-NPdwBhFZ;(&zWyvfTl=s6e0f)o`>i8b^~Ds6cO>EIK&-~Soi#3h{vzTgT1od?d}zxm$Q?caih7M$r|@VQ&e zXI-3UHk8uA+pdZEL; z91@b=zEv5-wHE$m9v!M7``F;+lwbFp-?pV6V?|fm9Z?W=-uqqP6N(;JNnN_#UnP2& z6RO-T(bzBVyUwsh{=bUe$3T^DUn+;v)GJqI(72`5fBM!5yk4-vAB~B;Fk+ z0-^;jA}|_bCT8R5CE*TY9jdXT2tJ{|p`89rjwv3MeBI$E!eTi+8u~4x*~Q#1N&a$q{{A_czD37#b1 zB^cF>WuiHsRpyUKQVuC1qEQZGxh-W-YLX<1odEVE6lxtD&eB=>lc|s#E!ERa=H*AirQQIHWoo8N0w(-9)Kc=u zC34x~?VL#_Uyi9|dtqj5HY8-0W({^xl#n1v(PeKo;PN$NA(1BOOlB()XLCB|b3*3; z$)%VLr*pDY+r>|ED(7N;(kPHx5so71k~$KPf*+GYDU$j?ky2@u#%PU3 z=@BVumU1a7I%!gNX_)?}15l`yn(3KhXb&i(m>$<}iV1$QX`B+>m#(Rt+NpPzDVpl( znfAb)($JOyPa^EYJTQTuI#M8vgBK{y2Mj|tC;{Uzs-q%=B8Y<+geY3^sG({qeNO3~ zda9XX=>#1DC+NYYT22ShK^sH?Z6j+2qeQejDe{dgb6&uI8=c_cmORxgE)-CI79;&{D43# zLbsA@H-G^Ue2WNZYqx$w7$5;bWa~3z7l6AZ+-k}J86!RVj>FmUTQcmuc!1fnWx z;w&qqHY&3k1f^Q)raJ7y(oLs+>cmoMs1i;K$Uz)v?8b7e!rl!DYyu~UtjJozBdCD1 z+RdnzD&iCX9=L%PAcP0NK^r6is;X+=2!cE4?9RHw8K5gdwCbZ}F|C6vO^9<3>? z!YsUiW!NgN>Z+So3Y<2E2!#HEu>R`k^eUmkDkPvo);0$S1Oqy3L1T1)*MjZ#7;B;$ z#KAV}vvNZdXoMql>$MU@AXFp1A` z-a_lWB5vUBZ5V_O0X#yt2Cg`C0~8D_+X~JH(84tQ>_52eq9&}~0Kzj^!QY53={ANU zM1$u-Z0!bHo>FY?&gkK^z#PcI?`A9<0IwVrE^g?-96W)4fUGB2Li1We$Z`TJ5P`xJ zf+ZM1tW@vy#>xu3!5W0CL9Boqlx^VTtUI*B`JQhXl|D_I-@D*jCG>g;M;oWL;%E7q*96*BS0(4Itd_%!!*3BKo~$E zw6F*yLpRv#K%4*=Nbcg2jv|bM7?f@Y)Pmgp1HlfCvrRKn zu~ekKei zRD!U|0|Ym4{^?9`DqF?_M=)coas@XkEgb6|S8@0_t28Wwq#6Vxc*8D`ut3-_;ci40 z4@3wALpBtuq%^`fv@SyY!83?KF$Xj05C9}_!!jQP6bsH455z4W4mi_|G53u(Glmz_ zZWyaGMB(m>>cBa~!#hxcnIb|w!~++Qai1zq8^7_1#PJ;CMjhX=KNaEGs08LlzK36@vgRI77E~!x!8!I-h{Qj)OEf zu?j0f-pV!ZNHG$VbwP}PGEjptGio;!vPO`zK%ldzWbQL>a}rat5sQuo2tzh&1i^X$ zByj6mqpZ6AYcya%0tf`%8Zi=o@IN%dHx%+)&npfagtO*r$-wN80f$imvC!m zFu{g^FhuS@7{KawYrPgkcvov&2eC$=w&UV3Yv=2}F7@hiYruYQ=H{!TRx!20H+cTb zH^73yCa-gV^AG|MX(8BiJeYxu>Od`^13p(MA)o^qNI@C{xZv<}L1e%k1g{;eZ-!@V z9Kb;+$1%sofyYYlCK$9KYl0_K@(EZ%Cq(Za-0=xe0`x{g5g5cDJg>+~uO5%=9{+R(ZGGn;( z_W97>p*yME{H=f>;f>1L);#7FiiU|aBe!&juiLxG<^9#An_F# zb2JBcV-v(RKYO4E!m%>>LG-m?6LvL|jtq;zmVa&-1Vc3N0=a{(Kn#L7(CaSzLRvR0 zqE0icdN(zEFQXE3Ac(^^a62%3aaq$swRZzA>;e{;Zll)1w(kNn3~uq_vA9cv!d5ajnWz&Ga7{WIvcfD`@@jWLWGLp-oUJfMMsGs8Wnv4*a3;avDYut3JzfgRKV zi2p3c#zE$9{u}Fo9n8V!Xa2gwGKvelkn6!fjOrykf+J`Gsb1>x#{ML1!X@CiKoEc) zdqV6xg7pr9>~liwH^LAUIgyKilIOcX6auNHupYC5BM|?Q|HJUlzVRdfKfHeI&;H8d z#v!|bVvjkU!z!6GYXNYt8)QBlxWO8PXqc<;9K8ShN3;X}-#J1I!k)K7FFXVP1B97> z10}hG@()n2X6~%@0+VE5M2QnA{-adNNW~ABP!Sm@0#m6~u2_{qRb=4CR6!yhDs{@0 zD^sOnA^6A0RmfMaVgdQr;gU#_FEds&dK76=rAv>>c#**Z1qoBBR<%miK~}9>9WeE3 zRM0+s0>73ud-mTUvGbe&@Doa(zIjUqR?bqpEu@qSgF!(1ZRY{KvR)mK&%!6e+@QphmoT2DyP8 z>Ez`QvOXX0iAeyNwo`8T^&^iX5!)E)mm-6)zuzA zO)8H#%KL|dIpA>6fL9(RuI4No%bN8t0U5UU&>7PW3NVfw`SaJ3rci~-dU2nl1*sK-Y*g~Qe z-dH2R4(M>hwie)6!%P_Qa6>|i*NBLPH`r*yFozjNOAvPytB3?~+>s)p6BZ)tpMnk= z2oZPkI_)BaBhuuQj0m_S=yU}s1(qdMGRy>|?;IMVpHVqNqKopFjRaFvv2WMZoPtWK ztHiE~tFj|SjOMPH>Hw~JZvKa|{<-S9YiG*RKI$^LDMD=Jfd-i9uX$jGo3FI<&Wvz< zIHQa+wH=pewI)00ue``@!|uh1xcl-riE8wzxkDxT@$JViJrM{Fsj*g&2>-#ck#Ve0 z3P%q7SVne7J5YqaZmNnC!v#B>&Bs6|dQyxB#TaIF+S12-BAc^~aQY{x z=GgCj`ydk%>cu0uV=~s~Ps{S7?+-2ZRL#0?;82gPI7mPBX1) zv5Z0h{SjniCPaS^0lap4V)4v8&}RImb=}s z1e=-2;pT=H%~bv-DR$9EWA-K%UIMER%Rq-c;^NN484fcMVVr725f?H&7W9eI zM0WQKmbz4<-+;zWp%D!Yaa)RX@kJfwJD=vlE_li$Dt}T0-_v3&O_@I5s7Y3I1P-P4sx@+DLu%mE`*KyXhXpp zZPbDEyXys!Ggza-aE<-Z>O{Coq(0Q*7tkOO3|08Hg&Jx@%jgDxfZAEleipQ$742wA zTiPCy5&l+2M4}L>NU9Q?qlxf<#}Oql+jz+K3{|}1YiC8OD58M}vAS(Ln1}``pq7Se zgb^I&_{KQ?BUyGRq(tZ_tvm+u2X>60c4MXpNmybNoM;70%%z8;95Nz_RE@v5rd}fX zp%Iqo1SdE#uSp8@y7-0US&-!h^steYL>lE!=D>y!veCfMr6mFCu!fLn0Y-NqSuJnb z4hvg&3NQku=i2d0g9KA&s3~TWmsoG14g%Zo>L=miOW2=6cN5jL@jaAM?J*iPrFHko$qX%Aovm&G)T|1 z{;0Vucx1!ErLt)vB}Je|SDHM;hIy+LlAq}2(<2;Vly)qIhBS7uhipO$1kXbtZxmru z7>X6noe7426lBsQ6pJ23n?(cu>NxXE2^bDtaCjY2ICy0}F`uqw4$92FtL5L{W& zYTj4mff3ITZzS?N-u)(Ha(zXlIm7|s2~T)v>VR-NfcaUYsJS%t|4N6K9DTi#ma?a$^aVCWyLM($-NZImjO`Z z!2dYQiEG?HKwu29#O0ppHp*qOWCb%P1I-dqbLrM6oL=k#^{L+l#5KP@@HOG}wC-hW z@=WYY2|^qNB%v}gD0^}n06V}bcD5l%5lGLedVhe%ZWa-u<{*{;!u$RW{hFO8VRPH` zs!u)Zot&NQ9jZF{dqk6DJ?uv#Fmk_$4fTtCeHYqVPPIWHU>yw}K5Ks$%dtQH^M91+ z<8J!YMAYQx8mOjCC~Jk}&(|P_79fYYFUah~^F6lIY&J zNZ$$}9*m3K(BU1ZfVZ}2RI;eGfQaDAgN@+84rh#+0u1q-dv2$RqZ zm(VoWh+_I{aDdJ=ET#=^@CI4p=@LL;nBbE-NnvcSEK(-(ZvLYg&LHPZ0P6_i!*C|V zc*g4_Vg$5?B{ZQFb|NMw(G@ZR?Sz67W1=Tq%mB4%Br?$z%PGOs!VkJ^WfJcn0;dfx!N~pz%ZO}2)JY-;Z!OyJ0^RQ#bU^-E1fnJa z*v^mip7D4P(6aQ=NnA)G{tSS3Fp?Tu(j{LKCSy`2#qEc1 zMF(IDiAE)gQjiB+a05ZGxAH9>wnzt>p|^yn-?~VOr05-v3s=lYhLp>>xUnKKpy4tH z2+=|a$BMn+hTtmbGahg9{(%?iA(Rdw zE_O2_{=%QA@f*P-3=|N6wj&tO4AQDm&RQ-08(Z>(<^&sR;Q@W`CdQ$uDu@p&Wpidt zB1BRg6U{8<$E99wGDuL>!on5eIN02SPf(Vsfy75PI)5 zxWN{lA#;|~BAt^VTSG+oIMrV{pF{LJNg#?#!DQU$gT}8O?jotK(2cn3GjHp$*s2#vT zNtJXOe)3kJGKQ23y5gW8E5ZfZDB_|e2)`0WG~rz0V=br4b1W{sL@v@c!4<$Q<*pEs z-bE8O?q~jmH@tKVO)gCJBV^V<58VC$!q#ZO2mqFrXCf2^3+A9tQ%MWxU@%R?4~?QA zh6xcP6T~1DEiki}Nc<*5Eicqh%q>CBO445 z1lqg|s&&Re_re5ArlAvmLJ^{+JR5}; zupj^;LM8LlCISHrrppAF;Y;fEvp%2}oFJ1rAY9WkP+HAOQlvjlsXC(J8A9n1q+uD5 z>H`L*b>1u@%CFbew48-Bq?n!^|D$6HZKVM}yG`^q(NqeLTg+gxa=WW*xJ z;X>QQeH<-DnruXF!6$+ictU_^HG*R`6lDJ)c3?=_VAe&kLpBKIs754XU<48dtwy`n zYrht3!$Dkp|yCcZP#s7d>{rRq6Opt4&tN$ zR)9|kzzBec1>~Slr?g7FVk-&Y54=Xg8PhIvXF#518(1RP+PgBbR0cn2K#>=&G&M75S@>ttO+sXDrW z7k1$n#Nj?$(Kq-EUHj8&JNSc<;z>%BJJ>awDAP=gTuGmXefot1lg_Lgexb5bSUG^985X5j0g?J zD7X;eZf}MCOh-zK#o=o3;I!k7Xs`z5K;i~s1stvpcz7wM#S<(+n!$?`oRInmz+Eok z5gcI>HbG9mg%e(`n`WmhN}?lL;w4<+6<8u`Od=*i zB5CH16myJyOVd?lvxhvu^6sJ<_?N zALIcFaCNGyx=$;w%E(}#I=~qCNgwiI9TxY2O)?^|!>>djriLea3_51HDg^p^9HL>d zB(Sj-!8=3`u>QdSP(Y9gq=-37qrS6IP9UJ(jPh?f$PN zB4#h^i^aoc<%rt`;W?-C}FD za?*%yE4ScwRD@`@7W@xV<+f}KNNoj5qqN}kbp?2^2RVxX^x)v)fCfdI0P+Bt;lMAM zVp=lJ6Ih`SCJjasA?B+25x}rGD52%{APU!`a=kR1pD?|`D|2B?=2{n_OrXbqoONN0 z$)ASD=}Vh|95sfH4{J9f2w(-^fT7!94OnlM`p#h>n!<3&&2Odv2EiOE{_F`LGwVY7 z9|j?LmA5o5qU=uL6;!~WKb93#dKL5_UuD|J6G5c`eP6=7dPTx8JfP6W4i)l1$GK^J zN3(rplx^l|#tmH5N1fD5-7C=1)Kgv6SDn>cJp#$rR*I+tlgO4g@C2A)w&0;1oRYUX z(85n})=A~oZR;JxfQ)n?O8vD0G|F@zH-K)@Dm!P>9g+T(TEx1HO&J+wg7 z+rwSl$DQ2EJv9&$;%A=bc>vjA9_I&q)!6~9`EyB@ArPdWq$4dzEp0W?+3rLhUpuvL(6DnNDu%W|;5C_V5k--852^ceK+?dgU z$B!T#L>x)7q{)*gQ>t9avZc$HFk{M`NwcQSn>cgo+{v@2&!0ep3LQ$csL`WHlPX=x zw5ijlP@_tnO0}xht5`=)tay>5SB)XVW*tkmtl6_@)2dy|wyoQ@aO29IOSi7wyLj{J z-OIOc&#e}}3YHKyq~F7c6DwZKxUu8MkRwZ;O#ZpD<;$2eYu$dfBadmHyg-5!@qpH98H z_3PNPYv0bjyLar(op*%(y}bGJ=+moT&%V9;_wb8{7f+ac{QLOx>)+46zyJRL2AJM_ z;z1{pfCwh2;DQV`=-`78Mkpan1UhHngcxS1;f5S`=;4PThR9fjxLqjXi72M1;)*P` z=;DhUk_eiKFxF_}jX3706h}kw_+~E)MThAHNlWR~gHmbuwy{^pu&w&~`ZaK1i=A3xuspp=2_UY$@ zbv}3JpM(}_=%I)vs%US4f;H%(kVY!$q?A@_=~<1Ar`x5RcIxS;poS`{RulzBA4Xk{ zs_Lq&w(9DuVrGi!qp;R$>#exvs%wX`*0(81I0P%~u*4Q??6JrutL(DOHtX!O&_*lm zwA5B>?X}outL?VjcI)l8;D#&ixa5{=?z!lutM0n&w(IV@@Ww0ey!6&<@4fiutM9)2 z_UrGz00%7az~JK4tAPjZs_?=LH|+4k+#Q@M#1vO-@x>TtJekC_YV7gHAcrjS$Wd|p zE6FIQtn$h%w_K8}{~(;O%QV;iZ1c@HGYqrLIrr@I&p-#g=*~O~ZS>JdC#`gqL>ImE z(@;k(_0%6`Dv->(QLXjXTzBntdsfGcb#z~sZT8t{r_E@opW!yp*rTZ}_uO>XZMR~L zz72O_clYi0-+%{RGTY)2wD;kNC$9M7j5qH1^?ythes^>#)Zz`|PyWZu{-H=dSziyh~nZLA?hr{P4sVZ~XDdC$IeS z%s21+^Uy~x{q)pVZ~gVyXRrPCtfOfV_uz*w{`lmVZ~pn{r?39{?6>d!`|!sv|NQhH z{tw}U9aR7R{P*ww{{a3Bpa2I*zyccZfCx;W0vE_V8T^AQ`O_Z*CrH5xTJVAx%%BE0 z$iWVJ@Pi->A@nBl4^weOAtFqn3RlR&7P|0-FpQxLXGp^u+7KjL`A6F@!jK#K@P|MQ zq7a8j#3CB;h)7JL1_ASrj66h%P>iA!r%1&rTJefl%%T>9=$Vmh@rz&#qZr3X#xk1m zjA%?F<+>OVHM;SQaEzlI=Saso+VPI%Vi){cgf3M`tp~+45l!llS>p1^O(p?rZShw%x1>1m{9~kta7|p0gdEkQsOf-xQHQYx> zO45>=)RPw7s7hC=QXic3iXqk8OJ_>cn%b0(2k0kDcgoX(_Q0bzO=3*17Sy5|^{7aN zA_98K)TRa$0ZAR=P{oG8MC@Z8On{sq_Ms0mc;o}2&_@|S;sIlDl^#>@NLkOC*6me7 zAM~I?{#02Ph;ew~b{`l{sj*WfnT*-Im63*XT|+uH1Vob|;tDaZuyA zAj#`qDL8^;jAIO@Elgv_QH8L0fELgY$2i6j4KVz`Ba4`?e!BsNz+z4W@SX2B!hi&_ z(&8EN1&1`8@R9uLSHGH79twaV-*I@uz8CoI8m zO9Yt{tRMpC3SkK&SdQWrx7-Teu!cp4WCd>60_(Euj&_*b9Bb2901V^hm%DLnm&~y0J7!q+V%#OuFjZ z#q=<|;f!W~B-#@!uQ#4?*|3!08PH%aNb-vfEC}Eu9Z&?q9gtsMcYqP+MRMd6TVRhE zAP{pGK{C3*;E$XD48rDh^As@-F@U%OwGg$l%MEP~gD=|-!2Ikq5B}*FYp%T1P`T#n zern1ij^4ooz33mO`oy7rbg!>>7CW2o+HW+pvdcpphU+*y#9{7=1F|e~9)e0h9O#)d ze#R-M{L7)%4XX_a0qWoeDU945$Q>KYMS}=3GtSJ*cfbG?Ve?q%T{k-)n>&BlLsLe7A*h=9d~`%hc(+*7~KbRi35INK>{`xe$bJAShIfZ z7j$ygMyRuYxI+MpfDXnb4^U8C0%&RYcW1|iFd}daXGI2spj;k-e`(+VZ^ejcUt3R9{~ci;0*KC4PTIRhjjuJHV)Dtd2pu)sit7w z7(SrKc+MCSBcKe_kb!yC4bi6?JeYZ2Cs-k<8+B#=f`t)}$}@{BXpeT~0gxbH)~I&? zR$Ukerz1`uxZ6_kY$S@XD% zAEAOB2wCBlU<3vdhqaOQm~DX|4%i@SsP~iHIF1hRUTFsoVW0!hC>jzu4%WDp&6Zgm z5Rjqg5i@CF{S^}Lm1?R62A}p|5!L}uscKP4jCK_Q!f;-a^^P7vg7}DE4~CY7WmzVP zmS2Em1zD7Zw_XqCgCN0|2qu>v$ChsCUuXVlja#^Kjum%l=MC2w5+GTPsp$ZD31MdU z4Abz5Ah=>fX_rR%n)4N5LfMxWh5(kSUzfF-A{i2K7mnSCd2tz&ArYEifP_gXMFyZ! zI&cp0@D5baQ-}}`@xTR4s8F**g&~mvcK~p9@MYwgV{rfn?dN+u76+XMH(D4L1fU6? z5Pm0M37r6OdysP{KnV{Q2}Ll14ku(0XM96;d=6j;oUjKA$_cZ8WFepltRMsqKmdtg z2|{)Wda!5+I%E-Nc>2koqX{=>$OfNChEN7ubl?V^)&kV$25pc9_eB7A0DU0gWV~jh zZqR~olVxjI7$@)x?Vx2|(1w!W4*rGJeO#ubF;@z9=n;ZY3b5dD1b_%OcM7k7e0b;) zCx8iOh6)|(5t@KzX9i~%ng}*m3aelQZ+ATJXF82$f44&b#dT-&K!}M*U5j{UL0TAs z&~$eu50vNtmI#Y?wuznST)Gj8RHt3d09?k{XM4toUX_W7np<~@r5j7W%CHwltD zsUP8~ca{#L$Xy&p0?N>jUsaYQ$c?+O3#s;9A2C?DAP&8-3%~$s-nfCX`U}jKirfQw z={TDq!FX4Yfh34t2Ev0X=&F+=2;OC(g^`Z#2#*x#Ja{JtB|3Rw5Dd}43&Lu8gCGtH zwhO;NjbZ>*_Xu}`NezpZ{#O@xmEW+dz%Y*>K>(x%4!cULlqFuZpsTyU475gY-6ajP znrc;8S7DG03R|w{=n=T)ue>S_zd(5;XtBL&tG)IN7AvfZN173as|4Gtp(YxNP_X0h zuepV*&!Diq>UC{NU*t-SBP(nZD|n6f5ukTpz}gMGz^jaTtKEPLcG+In)>vzKliiT3 z`0A@ZYhL3@tJuH{4_Fvi8LZ+E4Ni+#nZ=WDxv^2}v8u?CRr?Fm&;ztUt3taABk2G^ zJG3)fZ4Nt_=hcuQL24pcfkdmYyHH>bE3V`!Uu9QbAuA5vuv#LZ3&P3`vs!pzkzZBI zkw+;FqwU~C@|tAJapxz%lMJG60|nS}`wf_H&6JFe@h zcC7mhZ~MCYnrv13458P!j~ltrH4L&!tK(3GA2672%M9F*uhmAjL~FKN8?jvrgwd&l zDpd&F84qTlQ94iy=-{3HL*-%O76mb}$F_ z3%{Q@HuD)445|l*#tE132#%l$q-9zPhrpAt370US9zg)c=Lrbx2o*O73Csxy+z3PP zpbjbm6l$#=Q3#~Hr$7z#aU-4lr5=yunMBIMe3_BKUlL_Iw>y02}89 z@CyfSum=86rJ`^*2SuF3X{dkLryGM%r0viP&mhHlmZXIR2))n@?qChQ00zT&IC8jY znDBERAp&5!3a-EkrGN?&+@*(53a8)-u26nZ#R#qdrmx@%u@G1ta0zU>#&R5`!V{;Y zV_oxLeYkT7Ts03)004dvR#r!8qPSi77ij1wU1Bu^93Tn6^;(BYipfZTfLf@cOk0>* z20s7-c4lZIFbY;zSOfqFU+@tDNXZ`Y32M-Hh0t_qkdVwZ4_FWYAt1@yWe8k_1{NBq z`tZvR5X{3|%*K_B1!#GU_qoOUfY|zuHEX(=hnCTYY~?1-uiIE}Nsh@7dU#ibavKca zmHvVG#m*? z5DmTHjW_L$`E^-5%ezy1&KJszP3>&r(ADG?4Iu5*Fzo<>XV3qvcn96oR|(Z3{%{K* z=2sC0ogU!_l8cmht=HbzSdA^tIg8e*=B=t-(c27n%;?QT2#Th6)r#5FH%)M}O|qtC z04lI(uXoZZ4ZQx<4WUqF0$u1|^?9EjF#==i5d-|Dqa_QX zCx#%*h2dwP5}XJ{@TMI=p~y$z_ATHOY@dWYHXui*4zO`+P|!Z?5ofpteYf6j;EW&s z5iIZqY|sYJwQ(4}H$W;HBp?p%pvbDo#aHa49`S~aGsa<|38!Fj1aJxdKmMjuTBXo+ zThCbZC$Op&S;s3=$C#5_UEZw&n?NZe7^*GF!cM}Vj9N1w)O`30+SxDvx`l>yur_vrrtveo6q=wbp`Ch&}_-pw;*^~@hrRP3iSCRrVP222> z$r1Lf33>!*rHxo^bQjCzTZFZf~H5ZcW8O#1=X6~Y~%hB8K2V6oJ>)illW8o>@4j`UM3FZhS!0dhcP9?@iO zV0SIbSD-I~Qnm&x>iKQo1~-aWWC-hHLvl?@Tiy4kS$5;qrsLn|)}xiEbX} zZyxDkG3VQ*UA$&p&ShQtKnA1P=O0N6&43R0V2n%f%FXpCUcCNw=Q~0FTVT= zZ6FkCLG7yn+2g5$K5F>|nyM)L&oH|FXmYVQ=Pm-tD1x3ksj`Y#VlF|9sFP|iYBZBh zLkfMgsDx#@aUubWNG$EN)W%7SGtW2-Ou+g`>d3(SMB8sJF~=mcOf%0!Gfg$uWV1~- z--I*HpLn3NPCM`9L6UgR(7~8^%(zpBVA6>vo_D;Mq)Vx~xPmx|mY@#&XvC6EIKh@6W}WS|R$d+O6)pns za6`Qg=4fN2rRZ?Oq&e7#(X#$~4P)0O0_s?Ug*UWtfPg#N$ag3~+;OKJhaH}xU`gBw zY`t~_8ffB*9YDk#isquE6ml)9L}Uj-9(f>?HNqqn!$>FvmUCg2c_2+tIf5WVO39<5 zoqGoLO&MOuzyb>XK{{!rmrgq9C;8DwpEAyMS}s8jDx;!L^qFUgfg+lySEw=WY3q5m zBw=i^^+`iqi9Y`NZJ(~zhir)CzB_DbMPbM=L7p7_FwAro* zY>MbsymEcGc)ab04#E3O`fl_MfzM-;H!b!0lhG)atYUo}3af_QC-_E-Fpvoc%J3%U zc)IBuXr$R?kK^D30sazkI(&#MvtG|IcG&9zOWq=1O00xuhO{U-;Rf^Kq?$qq2K)Dt z83&wRO1DT5*FP&sKhipYBLySB^{Nv40D8tDjlpGvM5r5|{<4QW z1j1)A{;NuBqO(5hU12bNiOiLF^rSF3;1ShP&Nx)`Ixz7EdGP7Y?xfW|i-0d1=<`np zJs29=5o9nBq!JWd2*c*+Z-W(K&-D1$oeM??Bh7Pw5c>l@`R(EvBa$IT2JsbeM1vQ= zuwM-aVm%zbkVa8-N_zMw8`Gt3co*Reg=|Nj2SSiENV16Ss04;HLJ59Nq+ejvM?&hs zrhone%npGF{zm5&(vXKlWFi&0NJcgiE}rUCpZ=7mcrdC?B{)Z+-~o?6NrIE{*yI@+ z^(aing9~*^6dEwa3{bX%P-u_>B=6)%i-?Lm?}|e%b*an1{41AsC?G*%H7j$`C|Uj? z7PyoNE>{>v1eySsB_bh-VjU}77#Y{It_hK5*^Cvh8pt7tbqQ8D!V=re=0}pbOlBr* zC@XNo8U_N-EhLX#9`OJi+^|o6^3x;qbU;3}@Gg0JKo0VZ%fh&Dkq-dl3|sQg#R#G? zf^-aSaaq|#GJ!J(SfUgk1u00C^?{Zd#sncnsmpA}(Qu+Pqn%;vScee7{|%gENykgubzO>;#hT<0=EH@itLZhF%Q-w<%hZfRG5YA!MOrMh$daN+IWRMYbr#s<{VA6|S z6xOi22&PZv1RnxG!a|L;5bnh20QFsocaw#kAjBa)i;O@Skqx7I6q_akFk&2Gc#!>E zlb9M&ZH9sQKrjmP2WTi|5%>WK2?^kbWmF{$My$w9s=^?c8omC5FF9RxjSL1VW7EU3dThc`^Q zq^lx_=%xuaQBFrx+S0T50Rc2|i9OtkR>tCH$|BcIoo!Rnvp~QIG=b`Y6#jw}t{{Q{ zzJ^XxBa_mWt~93Q@~(QV%MDVnXT2QI0ywmR(1bROLD!G~cru{?c!(D~MY|P+wznB) z_^(7WMzM-|OiUR)uqjkw7)_Mo6|O*cx?SNclB)Eit5A2kV{x3DvG`=AfOouIp)4`}Vs{39_9RKw{Y(D+3!4wFnZO7WAx7R_jjJ3VfRD#{>@q{<8UL1Yj7^lQU+&FjGG!)B}7flo~(9=oUHtkRP()D0tSgxRcpqLk zbQ%a~(H3eE0%RGPO7MgtARJm78Ame{cBukoJC_c)Cw;+}j4+sb!Gkr>40w`)JaB{9 zQy6yutBUXdVsHnDG68LK0E!w2i~2T`v4}(98I{pOhAJs^i5b^=ha@VQ5n2#OoIzWInegu_a-p`D@#g83d&iI01 zxRSvTgZbj22N4JL1HNm>M!X9XAF-bLVMf>*hF&zg_@R&=@(JrvJl3PdcVxVIG$RLa zM>5Qa#lV7O7%uCXM)7H_iZCta=)BjsBQXH3(33p~d5G*lzu|Z?(^|b4N-=hXzHxxX z?294J8i;}HJNuZ144MiM`#mVJ9q-Es)VYl0OP>3*Z~;I&4n~U$8!Q-T(gRuR z0BQlJCpo541GN&^1Xma@ajCUy$(9ss7ENd-f}j>6SV0zqqmsu9Qr76XAeoQISXL;x6wtwD$c5COhvI0xVy zu5p40@Pqo)8$V=-waKr z_8N#200XKF3LjCQ{^FJ937~P9279v%MZmygToYQrf&d_h?c2uM0fWKx4`%o=5JScf zs0AkoPRl^ikDN!&xVqfXtLBJ?XArGNkcMSwjSonfz?%-=;1xiS6>#he@aYDrxFd30 zyv3jfQJkVI3W@qKfIyIy4wwO+Q$37O$fZk3_fXM-K~bIyfm>KiFo}lHOx;yjRNwzT z{23Tv=G931_5c2?owI-DQOYul5%*)&-Zsd*Z*`5_Q70h z&FsDQ{eIocVEYstXBa;3<|o;Xq8&CBfE<2g#5aO=P4zn;fC-q5S8wtVcwnyY4gGO$g-{wGYH&-IO_` z3i_<@#Q;!cTg#Ha1u|8Kd01qPd3}kI{i3`KBHpd+!(nyv`%?O?GGDl>fRvcd3spj8 zaOePrVHhTAj;g~HD%sy}5rHb=iwZT>M2LZFBR!@P2qJylVt9d|PZgRj)UgV_#-GMD z2~r|;Wzi@z$TXm6j5+hffIJ`1)WWc+E&5PJs1^p>$5i@I^-anmVqmRz-@ey$MOYzP zTS2v7Emzm@BrGCpLB1zUY{XtJ5eoXCOXJo7t(u4a(HKdP1ugI8U6m`;4bDJ(X+G2E zAK(-~_`nN_>*4g;Be@tGe<@H5Hzo%t4%!O#drbLZJrqyRkPNmkRXTky^3Fr1(tZRU-a2O_`QNo9mcRkt(ZP0}EU$pIp*!IwuRaSF_pR5!d{UheR6hbvL3(68t4w8L#4zeGilQm4N&@*(ZV z0bIG_l55F54MzG}mJb`lC)$BOZM09n`o4W$=eYYr_4zw>rogYSVX8r6r`3o zE|vm=z2gVGr}OwkWC=frm*|5x`Djy?sY{UGEZ0@rzk5%d zOv^13@IuF)6qb*Q!IddXEF{(;8*Ui(x;6rMf@yRPQg{1lu?5nK;6X9ZQuhTZ=R~Tu zXlU+4dQARQSq5n@fbE)Xl|xxP+=T;@K%eyohUOx*IzR(2ShZq75pU3HoZEbZBcph7 z{Cj@}zKI&o#AZrlEy^di;H2=0V}%8viY`;sDN=+(>`@{TqU>{G+;hwhDc&|lo4KLC zO+n|Vn_}PnWoEux49?ZlKGfrjN;X1Et>mgw64hWs%dw=)wjm$BZ>CX zH!9oSK|kjZu@@*aeY{p#Lp6)LJ~t(Rf4~t}jSiuxy=jG8L1P2$z9)h~Ua?eu9Vp2= zL-NDFi?u=hi!t7I;V}`zy}tlfjWN{|G2V@h3jHzLnhgDO(M%nVpJOAxOvg;`BSY_? zkwpwYf(WnU(AmY9Ij!(N`iht80!zfe4{Xm+1b#uJ71!uu?8b3kFRE1WP4?A-kGC6Y z?KWA?6~)BDNZ<^c`{ljUbu`;-z^(2BS>^y?EAynUu=?Vp6qEo>jfbHXgBfL znrqL{#W@H$5yiZ-IE2#;&1}Vd2S64p9?N5by@_L%96A{`*L9wp=8{sr{u5Z9%6ShM zMH>MEpWtoW0E|L6#fU~Mzwk|im2N~5#Kg-q*C`Clo^fngTo}CL9p7NcJOz4$!?>j} zAZ{E2Zq$Rv=)Ea7f8&?V&z{0g|2Bb%KC2}5>o;m+qMZPg_?mesdS4GPxVg=BsJqzP zh37-9$wJUM4$=BJBaf4}uc*XME zJW3iOkD?OkIe%a%D4|ACC3V~CrU|k;{He7-X}R^itk9^IA7~K*V(330LH9Co>755 z6&feX)#&-6k@DG7TSirmib_G|-y8Tui{3Ki=%U;j2YM3X8{Q_%MM)Yi2J8K9qk}5- ztw7f}(s+qeh&eNC@3P+Bg_)*gzbzjCk5)-axy@96MXXK4tX0LNCD$**Ca8VS^z?hVWa+;Cr@sj1UE9fhjW`(5v5!g67-cN` z(5f83BSYNsGN8>iz(@e7==8Pm<3rcjgCH!iZ2CdCsqpfh4EJF`A|Lk(HTT=Phk=)m zBesE}Vt`j^OpkhRWSYu}&C#{OV|;u+>&^>SvkFSJ5PyCue~Y|*ln5PF{`=MS?!&1` zmomuN4Fv}hN){U#!8TG-QZVamDl`%F`TJi5tVrkna2z)Pfg7mb@9npjh`sDP*%DXh zRFwnE;QG+{Y-=0ys7b&eV$_vzq%>;Sl3}PAanAa*_sri>>FMg_QyuTq&D*D2tDv8) z0UsL*c0WFByqx*7^z^rQvPavR6c}r-LPNl1cLo`Z#3C1PIyj?84960&@;vf6nFyJ6 zg~n0;iDDQ}VW1S>9&swjPUU#n?8L4EYJ5H2t>|tqxK-x9E`&IwKp&VYk&7YXcDQ1j zFIUSJa6Y_xgHNYhrCaZC&9PW#^wI42{EBnA(Q+(}+wtZ|lag1!u;lENd#%G`tF)e) z5X2vFf3w&kGRtEgM+o+l-m>~9Vz-<6QjjX-q>d3ZHE4cMbSa9~|CTQ=hI$Z08i(y+Af~1krm$8P>H+JG-dgE$gL zvt!KpfdZ54@4E#)b9X%oeBuAh^>X|w>C5aFvESW-Gg1$ag0nDeDWN%vC8x_IT+$w) z1x<;+LSJ>%rG&q|xC|6(cTIH?UNQ~%E4*xxASLqMCcmC-@s(?k$f|SKUy(KU&r+i6 z-XZzz^{TwqqCbKj{)%pfVM{}|+;&W>Hn0$+z0jR^5(wySs=BmTXUuAi&|a=%uh`GR z0EE~9{I}hw%$$T^@xz+;z2d*?yXXXey~sm|AGfUrOPqZA-7C@Dv;&nm9l(~6JRhMB zk-Tuf3uZiF>jn3?L#~W(2J<6kQU6f^47e!sVmK4upUh*#NO0OPf7HC4mX$3Nnl(CJl>o&z3bTg2(RF80bG$oilGEfY$ zjq;~8CBBhlq*|yR6>e^N=MuSkgQyz5kTXihJBWMY&5C8`zQoKY#s%%S&5NVm2* zvmua~%gT@6V6r)@y_=cG)1JrZusOT;k(n>z6Q>zgOU|eyi$FRiyCp|U?sOoF5Paf` zoo-9sVs|BHRos-bM@#<3BV449ecC;(rGOHrN^GHK+Pm2^_`r!(a)*7!Px`d*s+(2% zvS#M%ch6$PBdaVbM@KMLYY75ficPLhX*N=z6^<8lFinOv7pL1=N>*rt>+CfozIRqi z_m@pY{+>53zO|f1ie0TjaXzEDwSp(8rp^FsA$PvDQe-4U!?kvy@S?RUc+$R#ubL5l z-&(CCRVxst#)febS8YhNrS~e9vnHvwOr?jzphaA&0xLc=z=I>Wf0Dk5rJ)voj>Bl7 z)?n9NSo?l~_|=%&HwpeWanI|oJpi||Lq^ZsUJD|o#)MI_RiCdKWbC(+h_KT+!=eC&u%z4(zs zq`FvG-{i&n)%A--+d70Z$K5p5>X*sumVDY{v$(v~RR)nyEtd|and;yEP-eIG^zit! z+ck^#~z)<46`Q0MAI!xg8 z3pyK4PKj+LtO)Zd$L&G&jZZ~w-N(Y*p0@9WZh3A8_IvmOZ1OjsJUWJ+py|Q#-7I*X zqR&rhPu~)2sDAM9NoCEdM}5tufKs~DLuuw7i2cAv*^)BGfjEzr;@Uk5hYl<455)d5 zwwy9D9Gde!@!B(9q)`4eDM$w2o_4gM)i z(Zbinm%5@)R>0i9Xo-iv-SZ8V!oN6(tO^jYxo0Xi!y_%lR&6sASCz zFe1-!Ira10bEaD>qkO654TuGDhqc9!JI@YL1E`!Spx#j!i}0 z`|%i&ycA1i8LE*++N%fYCUTy-Jo?e-Hq^v-(Vx6X)Su6gIAG3J$pq-C&~Ao|`- z1!8ojP-pt9T!Zdo7NYJIaZ%S9@7_LhTBNAyD1 zPl0qw!fJyyRP+eEL{UF_9@e|M)4$2`M|(JV^x*4ji=Y{v2M7d=F5W(CXY=7D!wkMi z5dw|bvu!DmVm`}odTihw%jVee4#3|8Wx&}@w(N;^AY?vB{^`Xi)Lfnj8qeSr2_S+X z{zlBaLw&mb0rjPwj~fNtyrv+)2JQ|+aZ3KFd>OP4^;j#{SP)6nPXlPHe=#*;2Wr^l zgd!nTbtNVp(Lj&Sn3W~#-*Gqg3OCNzX~x3{?rCXo;50Oll7A}SKnY0^t04-BQTUcS zQso;c|4-#BGl!Fj{HOBKteYTJKBs|d-y(_D2&!7-Xl{{74qY%(Wlvp-~J&1-kJ8<^Jlni-34Y0$DJ8P~RPk~XQO<)w77B~Ktf3i9zQso(!& z`@-NzwvW#W`2}%ZZT(~WwCU;PCKY_PN=?ek`tzy3Ej zoA3T(`wlF)R2YLFEsmXISxN>8B@^g*5V9r&0Jei6NiG=EsO9DHt4P1^*@drQ=A|qy zrEsYqUWu0Ti40sf%Te&{TYjpROa=pnVNMrjW)35`0yVc6wL&N3;BJ8s*U-Zh+CSYd zc1K2g2X}i+W>E2;?#Bl#L+XA9nU(xhW_ltO~8zWg1Z#1oD7GJ`Pf+bZn zDK)uSDOrhnVf9giMRrmXf4}e_SUCO)MrSR@V@>QbC_g=-&|9hK5w6Y>uOzNUm$7An zMExS^Rr7X>`sux^nP{;l55Mgrfjc?|R`2EAqTO9BGRwm6lwWA$Q4PbjiEP}xf0*mM zV%8-E@}Y9ma^SQ^U0_dN*3?XH%-2we0(gy`uP~La?0h=LjLG;7D_1H$Y~2kF@ybo>Ys0U zqEWQ)HpX%gJMl`vGcMGm#RHi}J3DhF1Y9A@g`F(Y-sBdxY%z)z4dC_~UQR87TNV~Z zGcK(&AhH~4w5pX%eC_S5O>I32rCD|`892W8ph@9HLw#3`xelRB4#8}UAAfwG6`8HHCMFWHSs3I#I^ z%S*ZeVYTrvIW`j{6i5K4O)2WTH>K$x(BiP(92wZWj+2+$H6{pU@eNlm3T5^}Aw2;q z6aknuESN|J%+X&=oaIrMfacmwL+sXZIqJvC2A$Q={6!=gI zmistzhD|36gcNu`p1*I2f(b;PJ>4G|GnJsZMnWG^9thuHqYixu zI}~hndk*r*#8>{^**@h@piIB)D48?hcJNQM>{UbipJ-VyXI*pTXwn$^ z<2#Zhgbj$B{ulxBQwz!+B1yt90pd%|6V)NDv57;VN!{xINW!j9`NSTV|42gLqam$w zR<{;L>n(_vAgh}vBTm?4WgWP??y*;qzL8o$i-{8&F41x%)7m0B)FS!(8LLx^ggjkI zXgM{QMOlPJdBX%42&X0x{O1wgX3w$g(3lG;KkOH~aR=kbEw>{%LU~p7;_uNA$d#O6 zM}+Vd3I$}VxD|11a=Qvq`@PW+&emN@8gEC*@7Q-@V z>T!V>D&ZD^b{;1uthKf#hBoq#KgpOvQ6pt}Kz5^)N*vpCRBQ#Pk1&>L`F_Iwy-FaV zn=C^t&#Ab}xV;^Q%G$)7H|4Uh*{Pz5E!nHAy@s?hrnhSi2-Lq+zswWm(Raz!1OI80x}})L2vo*=GzkJ`pIa z&HueJqGJ{HN5zbV{JTk-c$7cJY_39Ad&YpWr65_p4p%VAl}UIv1ta%i)#WCh6X7gN^8{)Q;qL2(+*4i8$7v~QwdGiO_T`|*U z@3Qkc9Dn0-(AOl^7j2){;d6Zm)I`2DNt6h$BY9s$ zNIQZ8Yp^;Vo@P*j_u=AuVx-PXwW{Z+D= zyi-D<9YZR;BbeQ-Y9Ee`30UFFS0;9Y>~{1(6S)q~lhsk@u{pZxL1$QUfO=`C)+n-R z2MEm%=g@!qVD4>W?_E-GawsmMA78uF*Hmr)a!OvD|8gmuX3wHymI>{D?2fc z9lXI9JuTN(@f!s#6;i2_kn~A3^Lqx5mkt8H#;x|p_Y7~g7S^`(F#L@ji+A@>vEJ+q zMLFKKd;gL2ZvA9cOY14WyV|$MsNowBQ2Acu*Y*xZx&&0SFDh}>ZB-A0??fv3G}wPA zsQ<1Yp5yc+hFV(O*pu;AP~lT6i@XQ~{_C}9LB3{}nIdM1yd>f({N`<#+d*j2j|HR8`@c-e`%uM!jf^B<{D1N)?lK43GU_!`iv&4ziqwydLSek zddzMZyTAoPQ?yIF9x@*ZLBS*&Hq=?DU?A0WnrG~{R82>h8n;g`HwQ|()KVPDj=f6@ z;aNAVIRH-Qg$TYR`?9c~M9n7WS)^~IlOj>|d|ihw~vKn+7)+CZsa%rdV z<$BnB&Y-nk?EVh4njycA!mEF`XS-0PnYCHP`)=gjh}XCjo+~C!+aMmowW(h zW(p+A4DKxqMe_ZNEv!_3Af8)%&taQLb@#geNr=KpMNjE~WeggKr%6=p#bmET9tC21 z4(GFB9m-PtMZ`6hDEDp=aAAz8@{hPSHoCDB*b&;&I2E*1qbe3r#wdclBS)3(N^9mu z=s8K~DJZL8gC(+~ltZP;onmT{)|EBy8Su*wp%%+8A+Q>IIP>(~A-14}YC{6Q@PuUj1uqew~`k<#~j1%Oia4# z-rt><*57Wud%5BF3pq{@a6a|&M-ca6SWE~I&(K_TTTC9qYMWo)oHksQwa9nl+%heO zUC9aw(p91m#?}BRc6>WG!z^O3qvbZ!fhS=qYFve4@9f05Wbqp~m* z^?QjnO~y@TOo<%EYCQX9b{CPNp00*$FNTsNVMAYe?3`1(omZoI9#w@SPlE0XWA~bT1kYu9si>%<+#ib!q&W z+}y$00Gg=4a}M@@wcj+BEL7nvnWy8n?z(v5*Db|AO-r4{3O1B(m%Khiy_CxzO8_k= zxO9f&#qqTIm#QZ{T!$NTcP}Ztf08`xB5m*(-89Z#^Bf5Un4;ARNv09h;at4Osfi6M z7dJa58Lx=}$5DpGuAN_jf=V8xm; zE>2_Su*@a|KQpgyEQ}tkg!=?9aStscM+?-#S|8?4`BEdFVwj4NdtPsy`CDYtZm}c< zT7h26rv8jTDZYFwk{FJipr=rRU)+&k>x31fP8pflPl#&T5^hu6vIxVZMb&uOUSKag^ExSjbd@|IpG4edroD9_vbNZam zx1Z4>&HRldBFis2!3HPyd=sUgB^o`IA2sjTje@Ei@Fo4Y*@id4m}WM2+KX<)^`JF^ zf^)v5xM_-kWfSyGPFE!8aDmi2PUSlnA*QOVro)@xG%F2nwBI{B+a3h0FdJ@x)!9hk z{(dXccYe^(x_(^gI}GI>#`Mm=5v(qJzf_+)5T~S(;2Fi{KiA#XSrFO_tVJ26Ocebz zX*$iBC#ZSAm4?}m%%>^}*XqG)$#Vn~yfdQOZOB zaPhdqsWnpcn!(6WYB?KD$u1$Q`h|=ZX?cgm!AgE-+S&`cy>zm;t&Hvg?1d}?)h9jx zR%jU0JCCfyY<-t3!+=U5_SkpTl5qtMK=SX4{OMvEN`XJsSMKvbN&+64>{H*2ouFZX zPn!#|Y8jl&lX299EY1Wg#!4Dg%dkyNh#mD~EylO>PXaWq9HLPcyNh!#OT3x5IGGXZ zTX?zSiXs=OsRh=|ysy}|CBKh}D}lX2k6sT3Dv$k(lsYZs(S4@CC-8~pVAQa%pz|Ck zpyBiAtdsv2Dcz*8g)aSzltxIHvOoVyF~t-kYplX>2!`SNlgU72m2Xm5N)(S7NV(&_ znb^*zK&?S}ZsT&$Ei8sBMaU~huU7ymI~{T=VfXiOt80M1;>1=oH@k>}J(KJlme4W{ zw+S^UmC5jn5hh0DO)_4UUcDQd=j&8_ZDKpD7he^LD}19kOK6>65-8O8Qzo{S(IyZe z6bDMWMA}VZZOpd9Z%ujC)IhfC2IQQ^LVVZ#bRwO=A%U2j~G;(`J7N zc4E45PEEgjx76Ymtvgd%X%*CJ<-2b`s*qrxi zjft{D(Dyo!O4^u-!9+f9R<|Wi9mAG5vB>Bf%{$>-CrvPzU7cC-oN!yam22qP{HmpY zYwp)wR?q%?U9dI@^QHwZ$yPPV#U^_3OvV!PIe>pHRc{h^)g(6`$?;uwaOks1of?~4TGp$Z1h4rjS%0V4cW6F z2ZyMn@;|is{M63suoyr}umBG226XKYvsDsWmY@-=ExEGwER^*X#eEtk9Q~RO6P(j< zn6@Nm_@cs|_>7Ro32I&=nMh>cd__m~9+P>J*~N71G@?PaH|3I}hA#)t0OjapxB;@b ziyaNyt5m?DvuHtAq!DN4%h;kCJ{#*qi(znmWr%sy&Ww%A(644Ad!of|H=c=e+CG1l zAs!Wu#f9%&C*P}bjPpf()`h>ZoRjUe9X(R(cDM^%BO|%{anO+hrwKyfK5}3F@%D^E zMAbuNjtOl45pOPTc{ZM{^xOky7cw)(^nA>~1uK=GGSi(hw~4ZF?zQxDj5JjXgpFlG z-HqAvEA0=T4NgCMl~8sjnhzgYu|->M&=<^W*m%_x;Go!&oimyAPsy>}Y79M?=n$X8 zF^Px5@s5f(Q0OV@K}fuk#yy~PrlflmECeq`;8;4wn%{T0-W$T<(M)dt084YX2J7UCB&E}3 zC)4)OVg;(60O@B1m>Nw%7PO53RAe@VA+vf{r!{y?fN|J#d2=lECQ~N<120)6WCg2Eh>X#6kPXlz zOjJemrgSi>EckP22yf#F6S@LUD;hx{`cBSxOy_`tcz+zadk>bSdNte;vGtCEONL+s zfHGh!ZjLT~^b9;fbeC{~NQxf>QJB(}ftr;fT1o$5Q&%5dwkI1q2~I+6AG)AVe7@XmWIcLKyy-0RVW*jn3fh&L}Az zmTiRME~f)7ARqgErDNwi@P+bG0m z46M&XeQJ*(T4R|=H#2yi*P7@J576gC5A-NBk$AzzU$&j08+6FZ74;icVIX)GxkfHf zcj|+!+n5GxgZnMEb+x%adaxf<1<#f^DwLqNMPO+_J`7fI!K#%O0Ro`DvF5jgKn z6?aT^2>2m_0)^9LVqU7&!x3Mrb!6iNKd5Mp!jgQ#lCBmg8J{OmsbzU$N)9Bws`P3-~DkuxsMFbBkm&5UszX;+^C|_}kVe(6j0BIx-$P2bkQR;SZL=@*0 zVV$tvU>MC&qms9E>j4`}f4Y{4U274HDq~CXox5-!(6I|n=3AGXnAPf`z{=v;n&Ok3 zRqgb4K#U=^5Rb_rHA`G{1Z!*{LdYu)abwj*pGwfm^mWm4brE~Xhdd{2%no}nO*TQ2 ztI~BYvPv+)Q#ipuFx$}dz?b|YP)hE?AE@l6%=%Uq%i+Pv(?$;)E*f2NcV-m=ibSF32H zu4$#7L%OR2Ikmt13(3s`A%9$2Y&_7X_d};F_op^B@U{UjUiKFrhT0OM+DQ%&T`k!| zw}@`~yTG!L|06lWNa-$Sq4cFVP@=pUHEF;zjAgNMgE=jpu)?dZ!VQv_ImxZKwBYg22T0-_NQ)3{5Dut1{_+g0bDTz z8WwxkY7LN@Z2_7nNPB(Ra=DD#+TM3$nuQW4CGyAyXcuopMh*C6LqP3wF{nu;>GR=6Q3poXo)>(!V&OY5?JFz2* zy*-efO>F5v4mOI?1(RYo5C?@)k&2BX^9#PhUTy^t+vCE8a~o7ZnU}00nrZWH{{yO> zh5jf!AA4R&NK~Zf;G`kqU^0>k^rL_?JXSz%hvtM_?WV#r@5lZRQ!^mWFF;KwqoTLoi*-tZ%4>{!?-+9 zwn~4x`VENJjriA1h-VXQUaFZ-Ruh_oqt(i;u4i>VcR1C*dTvcHN)tWN@Os8t0iAI8 zWkL7vtZ|`|mz&dT?0J~7_=}==8(b-xq`_O}eJ6iPCrAGopRu}nthFJ}EEY^siRc{g z$?9JUkv=1b&Nb}YqknUhy__Ejq5uW}JdY0C zMM&inJ;K}@Z6h>Bhlr!3loQLG;L8B}$`)p1ecbD22zr=-qqk??A&D6b&H6UU5SV}= zkMHD}s47ADg}OTd!ip(N)V!=FS<;<=MS2m+_U=nkn=t$5PWdlG^(h};Q{VLAKK2u? z?YHmR5Ut4(eHfsZ9Apk0WbYo-L{SZ^ske8%{@Qt!5tmy{Xi=h?cbJz`Vju9dhhK?< z+=$27*zf~{r^U2X|pH^qu&uLPnYlyKi@2*7Kbvv+4n4-SB%m( z&ZK8cP?%1rwwV34rR!Z;jO$rWPE=L;SdgypS!%(m@D4jsYNaA*r6y>i!(_2ZYSp4} z`hCx8PY}D?`kEK5A1suj*(#_GUK&P1Fb#aBAWtWGx4IX!aan;1u*ly5ryO->AAfQ> z&fY)(r8m)nHz{{k+Wu}5N}G2SA_Mx1Lxt~0d$yQ^x7mBQxe?p^(mTRt>?TrMQqqeS zfjf$3qtAlhGX(GINbkJ}-h0)%XNuSpn*oX=cI*)&tOWb+W`63yZ?&X<1|fciNgqT7 zAH>n^0iWY3>~n&==Y8^ZJ%@_ zQ09DR@5GPwl=SJGUgm;1FU>_V*XQtIgvCUd12a-}16dAD}1DucLw z5pw;i@7na~+M+L8<5{w(%#HKYjl0Yr?~p%!eb=^qKLccLqe5=u`flGn-RdF!=sdg2 z?Yk>{x`WHySICTJ_T5xH-8acRw1qr;>U)T*yYHEN7zuft=zE-cdYpTdsmlU|flIr_ll_SF~fd_LKGQ0A%xnWXIXoa00bV(xZ#>Dweyb z2A)*s)vvGbjr)?Mx^9lwx~xp53*eroyPxxAlBIj@ua8!{LY~tE`VQ=Dt@b6$^dbH} zK0G|(L;NqIewru%>0-^pv3SPKBk-ib7LmNT<0fH5X0aC0R4(HdF?7M;ZA+4sHx{uh zMX{Fg91Y`^2|O;)Qd{O;6{~k58?jbN;wR&b*8J!qR>^XBs@ADWbci_XG&P4SMspYs zrcH*fma0vrfmw}dx`|YrO}1Gu=9?VLB-J;@MjHmNa~&Ec-sHRVV%ipv72dtd^Vo>9 zE%HB^K)NFH-wO&MTe5cWXu5d2(s-UpyRsx{Ec@~_Ej9a!EVFp~$~+fj=B+3g%b~h7 zNzI|AvMAo6wzgr?;eA6dmSbJ>w3=gm`$oKDL+8n)BI5-B4A7^Fe)6Sv^-3@g**$MxeL7bB8RW(HK+8f zPy-DH8np)TtmV%~p;>L-b?+624^uv?{=v@Y zQ%~qL-jfFTrcJBxc90exf>_|3PW8*1dm*46Vpvl&7)ShV^SR|D)y^t$^c(LcE9ZMs;V4--Wg=EysiS z&jZg#FQXoQPL&3?BLlZu*Qepno(EkozD}0XpRkS;^+5eA z{G*er3P0rWV&?gi_Rdmr2;%;9CPekmAN+m*mRsoA!!sa$Y(I#j(Ffh61oats0NhX? zMpRdV#v2Pu(&3AsIV!=BiLJ*CcZ+1=f@4lF4HDP7MR7U7Q*;joDJI>bg?0&W9Ak%Q z4&7qzK5bp-qYp!{++!8EN(qx=hnYCs<20N~iHpG_Y-;ZD`gNtGA7V$i9NZI3j!Mb< zz@vQe?upi1Wt20qqe8Xr@0^^x;8%UwHS5e-V2N|p80ns<68>~25hR_j^VEFsI$gE`|n+Ke$}&8j^5 zD)o8RZpzJ3l?-hRzz@r-q+X$4hJSG8-9b7Nrj$Cv4L?3hodUtbYz!sSMVR(M84k&L zC_jfS*q-U`jNV4U4Irn4kNW% zN&;pPKYCf~RYescjIVPpGCdk`007lh$-BwnqjRfc9Lz?u>5qVpI}T*TWpUnW z)3ZP8SKtVTWJkZThHodTfR*6Meq0vZ#j`Qe9ei`~d6}j6vZy9c=Rlp?7tgK?9WBXM zmzTiaG+&`yijivi8;KCg9SZrEydGcO^#>T}O3;J?#gptpw=jra4MWWyR$M~EDg_C{ z>|Z~;@(3?3SV*V1WY7 z0mj>e9JgH0TPMF&k^m6+F0&{~l7JWyrmgoDr16louq~Xf9h`4%;8#=e25p^@L=|lA zahgr>ueu>j&OyIT&cp~iqnSiA&fc3Xg@a?&uIf@`V_qrC8MeInT1qSy`Ff_`D30RQ zGnJ$%>lY4$7Y!xU_e#UC3b@tg?}A$$BDO;f?sz zGDNGKKSdMWMzBfvLAV8Ka%jS)Og>9~9?Dm%LAdm0i(8jx0>h4fp{n3zO{B{^hSCB3 zKWcXQ7spqkx%1&-;}&24M4{H~rR=93Sl~T}#)U{(#2FfI_Z66<5xVGt)hXy%K5ckw z7@S25Gksz}AfX7dBoB+XtybSZ^T@|=UTh@-25B6nFyo$w9qZ)`4*ODwC>uGwN}@p_ z{g}j*_(X4&0v!fDHlCYp$%Xqo88X49RhTJ^o#WOdnziVnMGJK76Y(OX4|e|WfOw#=S*xxRTBM^J+2l7iB_4qQ%Fpk|CEXlhgEzX`#S zC0s=$p^XWmoFvx03engFt+y3;;q=jm5tr<`RsL#qN#1F8>#O(s!;6l(J~`zw zEPj0Libenoi$19ny4(lFLl?ia6zC}t{XqL2zOM_Z3pKxQl&#Azp_hAKl~46l@yxC5 zW?v?Yl7jld2b(W10BD4_E}7)632lZ~*@Q?j6oM2(A%=gKTh@Lbvt^o@n@sdryVq|ox=aP2>vxvCdD;O0T6}RaKA-{mhh3} z=Qe4i*^N7;MWmTX$x^s&#^{NiSHARA38`Hghc9O#!9!v8y@IR zcO=z$CpX&J=#JWsWWL&gz6)Uuy0rkTm81S7z(18myNCpz6X2U@2et2#tiL3rbi=54 zgF&m5_Jb1M98-zcY3_bkyZwJRbuj+X-4i zar%$#tV6P$p+3mSpQ}%{%UJ-#-2!4q$cjL^ouL+H<{?P8llR42s`eb_rVw&^a0LK+ zNC^S_h>4<1^5LTTn!Y56YXA9)nlQRMrjUmo zoZ+2G&R?t#j_&+PM#YN$hSKhTFy{%o9T7ix8(`9PhAqj4j=mQ~gc0{O#agbyA4$Rf zFfZyq3yejJ``(6)_|lIH7}HN*1h$?Pa>b*C&X(?&7j*$%w*N8xsro7=Q`Z6zmud)S z$t$BqmPN+ErN2c!ZN{(1!C#LT&Cixc-T+hVD*6DpAAMiRzrfyZtdP!hqt%We$b;kl zu@kN}HEeuU%SyO(UEDAdrR-27T2W47RQgsN&riHug9H8?%UR_btP)?8xv1N57GJh! zM9_&n(^2detsg@Mm&~zc|C5+>kDV$~G-F;7lV|@X&nPqA|J6#7X&&LxL}^KFvckQQ zS$s`dJUo@9cEhdIa2r`x{Qw zy#38FJRu*BI3noy)5+#C{I#tM+%S_j!Fi1@Os^e45}5gJnDXp|piS>0*~X+2#|Z7l z0@=oQd?4++3o5eDnjYlJP*I)3bkgIav_*gWc{YvM+s5_v9`)$`l9?|R}F2P$uCUHCqKr`Nw zhYUvC@+QdLkdR#%&+))y8&IVjg^MYMBXW!X*|u;F5I{6%s(K9^I4h>#Y%X7_`u~W! ztEjdbwrkWRc+jB1-QBei+}(n^ySuvw*W&J4D5bc&OQBe?mKG~5)WYWZ-hYgJn1dW< zjJ58}c}*3?=pEu}Wg+l9OIe<9`Jt)fN}lfOO5)}dHUPWTB;mb<)q6&h^2^tG3=+w@%spKizVF4x7q2yuBw}C$Wv-|8&c5_DH&YPAEtb zgjoQq?OEp4_7##mG<)E=lBrN|P8Vx2!CiY>qd^z1U%@nH@OG}2%6zb64sHpC!cj4m zcAiyVgjd2Go@-9@AMj*=8zI2W)#|;Yk;QEv0*!mYDwTPWa1`h{g68f88+o&!=34rX z1^4SF5>1`4iW*QDuWsG(J@>> zO}B%JeEmdOK~#4z=Mj@s3Sz1Xo$qjGD%R54z3A`x;DnA+0v+3ml9-x#&)e$3IJ9<=4`Zr+eM zEQzyi1;@7RH~us08}@IjNCO=H505QxD+;F^+_Ei`_B!n6luUYv(vR}?`FQ^%aM<}k zws>pF+0OQ4gTo@YPc5SRB0TtY95G;zL}!xNd$OFWhR`}R#N1~W?!c=7?k@Q7rttM& zf4@!_)28q&$o`vr$YcY0W~c;0W%ou91YUf z{XB9kf@STxl8+!?I&hCFP~VJD_y?XnFWfW?j0v{BEOovL3!I@WV%7#lD1q~#K~G`D zH>H8%)L3npEC2e6skWAsvR3#E`-y$}SyoGe4J`0+h?AS~*pHl?~2?B+}tJ2Kc#k8Ft>RU}KhM5wN#Jg@km{wkfva9MsKkGa zMl*jZK@3rT(S>)6@G1BYcw7uPeR~YOm|6VH#=aiXW*;WJ3EN&t9kM#-Lzra02hI_FxaJ z_h6nK7@EzQmJE!t_bfN`eDnN_5*&>Z_}iiEJYQshIsCSDvmS%io{9A975CNn_C+GD zDPWj%QfZwLWIpoe8r{UfZ~(oVl8`nkVfir;lQhBSSl>zbX6jK_d~qU5*qP}9mANIg z?QyF6$=t$q|6XbvtNokL>pyn$#)EiH^9SaG(zr|M`v4yEtwsc4wEaDvxof@&3<+Nv zfy1bL`=4J5LI~WsEl4YTV$>{sx;;#?+lYe57oL_^JIfIH$KJPtMVOyeB@xxie5ToX zTr5L>Nu5dy;xm=G8FyA}Mio9aVTV0%&5g;NOhu%ZrQb$^7rj3u;dYLzRo?C3pA$(X`?G)}OUyHpzjl^OmUo-aiRqTy`(0!>O z%FZIZk)Jf{%y#eX7m9>D&-Zt~JFZkJWHOt7cyQimG-!AD_@UQBSu5}zHDIm!> zDc3C@knDSRG8&s`s(W{QyTnPC%S}yubk{#qHc^s@>oC42ro<5cGjO+E;Ud}B-YQYd zd!yabG8;+gjU*Gl&)vY6Y0I(}gP%ok*SBRVci|(9&H9heXeu*uX&I z$B*qf0s-a1-Z)}9GB?R$)rLvXoLaMY$gaJKOJETm0_MgQnvbO1?;K<5iC*PGD>DVI z?(*&?mZx-iGIc_+AN$Ah(XCr3D$9G!37b3Sjm>3IK=A!ztd;C<61ci$tg(_)692q8LAOpUsloXm-aevaQPnnP?T2WSW1EVe zS${uTNP}&T#?@8&Fi6!YFPZxCW!Y)zOIGiP-@B)?YlZ>4?+(uT&#J`Gu<0gunEzU) zhHRzq#yUkiPJWtYK>T2vb?sQ0%C>!rt9dLfCz=Zu3{0VV`Ry_}(<73G3;bG!Is97?SXVIiB+ z{ijU4$;s+0g7NJ5HOabzW5g1tl^py;ERvhf#IH`RIT2O$o&Rjrx!E@S4 zKKjHIOl+J@bq2+2MD=M(aILpZv?vH9g+|RYDRZ0}!+g3;Nm^yNY}A}#{upt4K>UpodOeN`V+=eEjERPBSyNbjKSk(#>VvkG^8m| zkkypR7F;$lqn9Shj*n`Fr_*9GZ2U}geDM$!XXh|;`7%5J4^7rf#VGCAI}NMXQ7ysyVf(%0lkF)%t5LoOjMrX6|2Mt|~8jmu=? z`p7@5?@xOSH{ttGwyI-jg6C|$TF~< zj^aF>V&@LR@bU1+spN!Y^51MQ#e{v%cul5P1oBDJJ+-FIOUJ?o%g5-B#57{b(92+F z?WQT}2*iu)SG7EuaH^^bp?CqX-9q>H2f%0m{8|i7p-hgVqdvzLd2(X~rFrcWU3=CU zCT=06@C|TgAg2d{TCPb+3ylezaB^D*-G;dLeYck-AWoD$_~Rxd-D;;n;cQ^4CHly< zs3eZ(&H24Z*~EJG+2yUB!jDKk?T3_b{6v`Jha;jPj>6@+oXbo=6+P z6mgr8=>-$?I69slZ@a(e%{2_IGleFAri2Kl2t5z(gSZ2 z>6-1k_$Rv)0~W=S6i)ljZ?4qbB|ML=+1^2FnO5HgH zy(yvFKbbzL8I`+!m-2KW{K@1+?pVQmk>A1`B`=JZL(c^&LPplNP+3Ge8*#q|!S!;w z^u@UsKl&qzJ1;-pF8PgBK3olsuTwwuNTM`qC9`~r5gR$?QQ0ihpYEr9^|u|Fj(l|s zQB8+Im(dbr8q8x= zf=SL-zA}u;VENr%IbxTPaBKWj;|jC3v&A4^;+Ds+)mCWMN=Q*NL&BqA>&jK; z#E7i-+Fc1Gu%mBKPLOp9DiFIh10=v({2&=wL86%?vTz)u*_JDkGfvK{SapX@;G#f2 z|1^uvY1&3to(Qv09to!9!^L`xUp#T2*?CQ!^fhmdZzJkUu#2@w2cG;_8ItHF*NE?t zr(v{g@CCDkkbdu{_sm~EU;u0^9&Zj#cp~O8MN02-pnji76{*9HzlA(abuih6Ze zHz<^!1mL3Tf48t6Jd?mwI}4WDgPI`j@pfz&1TG3MJb@u^lMpnyEJlJV&_UkWnkWH*eM=6ytvKW3I zfq2Uca2eo*9tMmPVHBAmPm<@Q^DCh%$$G&A_|6zic1B5G_iNzwnFu7)FqZCF& z@K@!W-o-7%NP#P4mVb#XV2Ey$iz=~+s;lF~m58SQM$%b%5$U*25d7Tyb40?Y$#O_} z>Hu`HrvA%0;;Z7ee^)ih_eI`TuWF-8_ETlf=j#$6iFG^i^|V7~HT=R7`8c0y@M-gF zqtIORLE23`+ao336^c$XWcHoO-cM*A*h;}5koT#g6FfoSU0X3Sx`%#>z3N~mKDrlv zv87i+3z*l1o-|omBJC0_>ldoGS4X^Pbds226lTI8MZ1YDMOVYLvvo4hCzcCjbmzB9 zV{oOUr%7{sw20z5%MN9`35lr@j6p&&t0T$8w-S}BVi9h9hHq;z znrBk|(8i8tGagjhR+RmV6YP*)zLosTro@Jb+)DfX@t+#R*wIYr-xTY=SOxNPg?<#- zg0uZssRd>#=vPCH*u@opL6y~%&dD$Phr`aQls{4%-g}q&U>2Dx152Vx>hWVCW{Sgq z)kd(@M+b)y$US?sxD)cYf>5es7#p%%Bg$aoIW*P`ik_2#JN+QNI|6<6o2{N9^NV%a z11}!1BKFLhTC4-_1^#<#i1i;#`+JL`R;Z!IoAv5a4cHI=AN&XW-TVC*h3IZkd8Ens zR}|{5h7t^pw_E-(EzWXW$Z7or{)>}N0|Aob&g|k@{-R%mp~MVeCWNy$i^9y zophpouqzYsLY}H<7KYJ{`otDoVY7NC2J9fT5Pq3F-srBEsE%If&R#9itbGkZ<4EuO z=OH0>yKzheD6~M z^s{}C*R&(j3e(xdP|!PO#p6ZOu1s`6iixZ4uMo^LCWpWm6{K zCd?G3@fv{C{{b>r3k6;aNIU6sBT(PF@*_cm&}uto80l3-%r|+HhBfk%Bo(j}%?-G} zaZc#O%>oi(<`{Sts9_Tlyk4)~;-l3%6?#?aGtV>Ct8E&5i?5P1Ffa!>b76yRwab&H zrk{X;Mct-2#mpF>5TC9S1`P1ZT1ZbxGh>n!mEjEQpqKx~>?M+k6{bzAP?x-se_jGD z1Cm06p?ar*qi-Q`RP3W$tWZBm3_hpO#+XQ<*2;`lAgkHhubjk}=`remm@RO{v@sw_{oOad zi(0ZL)SrpvUo6$?!PY2U)ofXf?bbnjiPClwt=@zkQ(2sFS$ukBajIc)W^QrrZgKH% zevFYi>pfe+Z`g*;>r*FV>i+a&7;avA`bSpWFaMCgGT|Qgzs@>YxG=Z;sd4b3HEnOw zoP5)KE9ejoLNI)8!u}8JyR>}%c39NgW_V*tW^2kq2>d z!~qVhxIs>dYN@!nzYney8h1OaPJ__-ZikVbtcWy^;a!KbNtVA1a>@OpKIU1&W&eMh zk2;)T9)p4V0}(N`4L!FFgOm-UrVW#Y4fE3Q<)%7oh7FtMA#R$m1{MF4EqzI{>=|I-1$*jh!gF6dy8v(ON6}@u7fr8`Jxz- zXn?&PS`T%}jmdLlGBhgAPx<_S_zht`Uw=nojW+ zP6-}Pi4m6zyht>+(Y6N{qSw*MZN^Clm)lY2P9*&?ZRfFyP8r~mA$%d7zBk|F2PCFj=%&SlrmA_<=h(VV47K4+0Ql}owQXu2S3EnMn6T)&Gp@o>)?{>`vcdZYu90f>j6+YxhHh`w_0kF~W5PauvEZbp;rW!|`Kt`!`Md3AgF5||x&IE0 z=+Cw1Gs5#f&4I=X$m4}9?S-P{g=%>_C7k}Zjs6tjf?4hbYWKn#_QJmTg1&6NzT}1X z?1hi#4W{uXxH-oeuKLIAO>F5+;^|Eq=?(EbA}l||&Ge=i_NH9+raJVd?&~JIIU&FC zrlawp=kZ~X_F>$%DZ>3stL4KI>BE}o!&dIoitvVLdb2P4a2@(^-}vx|IC9jVGe7(A z^Y{u#`wD9L3gdpqF!B|N^cBtY6)XRKZ12li?<=|N`|8kF>c;m~*LS{0Ul|%dSsp(* z=?8U2cda2`c~3v3NI&IFKW_Agm>fT~VL$a{KaF8;RYc|&jb}e?JbxV;|GMi3PI`ZR zEq?<`f5T@-J@hYznf}J*{wD4Ix)J`FZ~V;<{Vi_%Ek$h1hQCVs;RSlr1p4p<`br1-X$AUQ1_pQr z21W)3Wd;V92ZpoK~zhJFwlh5SccSjhSZ~b*G7gkmWMR8 zhty<*G%ts=9)`4eI!@_tq-EHYXPA{r*mP#tYq^7#ZW;>{JMBao;i8E+Z;!Ydjv$FdEF?v2io3>C zM0~}IyrX%>+>Tf{La5w1IiW?~dqzG+BA%NagPQ_^*PxN)hR>0Y-X)0^+q42`Fw86 zu{1Vc=EY)R&y$5lUzX)EsYoCWyC3Unm0~8nMw1`gdYxvuZ2AY?|K(U`n*2G8TCs}Y!2i(9Ex}b;&KM@9*;pAO(sA1-x+pT zPF7LDwmOZw6L@TwT7m^Gmdd1Kak)YSKd)7QpDtHp(XY1JEXOmrLWSKnrSJ=vT0;Z% zCXC3C3!q{?>hlB)xOf~z2+^DKrE|G&W4`lL->l> zIuvA6SeZcKvs0N!69b_)mm-2yB_F1_Q->J@& z29ljx;kE=+Who79jmOLK&sL@Kgyd1if$KK4?TvJI_2~aBjLTcl#8qjLX=KlSV)zKH zd@(>W-Z)r9B?;i*+pihdzvduGt|?RcX>~zYdKqnMMzyl4NTge|l0gRaho(vBLw*35 z8&PCFF@1~@tRtF{v zp0wL61N%todU-Uv0TQ)`lTH@8S5Fp7WdedRNrI*N>k`>EsF}4oL$-;4sdh!Zo2_}_ zK1L~9*Tvaj*t*ytjobT$X{{1hv~A2uFD9fn3;Csm4zK8LFthq%`$L)*C3cA}Fv&*$; z^aa(_z$~Taqo|Gk*PQpv=1ch)BT3fT^s60g+UFtR)FH$5)nx;|&uIH0$;cF&W0XEC z9=w#j^Q(c%Aq#gXDmbAw$+!8U3z!`8EBZ-Hiv2Z`-?>|Fr^r{>dtxXo@$9=KzIiQ& zlW}t7Hf(_aHku0zi(x6h>kD-Ss5e|EzPFN5#enXk%|T}?TtzEvq`XM@Xxz$3u=AYS z>C!nC1&VTWliExJ90$V#FSKHiuCN8uIK?uALP7Nw+leTZc5Mue1ceul%Q_bL4Bt8u zM~oy{8eB6z-RDEG@iAUCzyv?jxt0`s)s`)wn^Cm1b z2Iq+k949$!I(dVAs7S{@y+q6Cl9^wkW%{hy3A4b6XABvpw7}uQp1WYqZ+XH7(HN`y$Etsw6VXv93&0;$ zDjhfs6g>@jL52F{WkyT!ilD9y~rpRJsx_j(? zuma_9yquL$=15+K6sXFw(TQ}4n*zR6S1Aa5Ko*ciQm7a=_zg#=-gd$c3u>a?s-RQm zAWzaflf&@^#VHz*56Crq!MnI6BLD3W+@3p$fHqMj3(S3vMXl6HT?Yl#x}@H#Nb*tg+-1r3NA0G7KVs~bZ^DheKrEN&7B~OR({%$j-LAcYB*A0hZp3?_Bfs|#QFEXP2cC0cr*5s_o zW7l{$pq0NR3Dv_ZTmtutAxe=2Ml>(MBi=E=)j*2^7m?4^W=gbGv-$gP&=EDWPXyOw z+NT@?hQG|W6Si7_tK5N+Olh7wKWIi!+=Q{*j*!RgQa`Saik;lyvO_SzcS-1^{ktr6 zx!ts6{juM^A`RhCaN>GZFp?3HB(?)R$&pkjL`m#t(Na+Cmwc>H%lvys&+?=op?Mx3iI5|>Rwks=6TXbj z2UY^X@@!TBX&`M?czw_$1u;9E&??5Ep`i=!dBfmuirf>cspd$wl+F}0TcQu4ETRiXP)HPSf7>a?y<>`L$je~^ZV%uh^g3kK(ZHB%hRq;5{FA4tcSU?+ zGT__;n2qFqP2<@}VL0Lg92WTCOW@(Y zs0d!Pv!~xS`vS6#{Nl9Cy(;m)OAgQTjbb7Hw%>o*VM!BHX6R92QmbB%`i%KPpuf9y)f_0O1xL5Hz9D^C}@14m*fHx zHPr@?ir4{%ZY3IxEp9zae)bj$v*MN5sYKw?4;hZ0M1`HP=->qu#(vM@c}#DB(aCR( zLlJ(-N}{raQ3OP{BhtwlX0p>jIw-{}K!g&fC;ssT$`b`&90P?hin>QX6a-);d;{hX z9sA3)D?L;Gw0ZdNU+iOa`IEJ}@?{o9i$&2cS#{y=7!mZLA8tUW+A7Z;B8RDivV}waoD82hL$!YOk#lnUrRBjspkd1QDMe?K6a{O` zJoZ{LMg(cx8FKiPaCP^WDhCOQrObX{Cyv!G014yBOJHt~Xh}rM zoZGyLzvq*5DUyTCDkvRU2!vv`xJHPX0%(91Od3HEv8(|fkIdtUUfbZjuRg&)m1B?P zXGh^zkP}oy%tJvbGB(x64Gd=XB=YRjLJXvT2JwL zh7PoV_S&Ec;owBR31o>0Wb%D#0d1J*fJwN^aTKAVICkV%K(fdjw6pY2sX-) z|4AH&NlWGdujMquN_`8H@hOJ7(KP+TG|`=qJhqhZ2GaKy{$`Q|SD2vl?ozCt8Q0mT z`qL@F@P1;*KeUWW>K=_9t&6lCuUidR!Jb)R{wTir2jGR?dwWSbR%~s)B=TNUfAhV!O4KHVF0P?-?YG_K3DtTH& zb}kujot_`Ga6#^pD4ha#PkMq*ktn9QCfYYx)*pnz)BsYpUxY^0OD&9#om%U0(dy`A z()EL0smsU&fLdUV^$m`1^7K)x^EUhnAVq6f)|Bs$*T!y%zkE9>?Pq-TWzQaxgRX=8 zm$TFWOvs~Q#SKD1H#2nTMFDZCiOf{X+OfJtXF}i1$4n`-%j05b#75Xxn+L-OPPND4 zm(My0d6L1FuP&hvAf+-h)U&zQ#uJ|nP%QIo!rt4+I2C9dkXaU$_Mlu=1sk=Ce%z1O z&UQiZ{D&MxGQqV=s-SI`MSUs9Je~`ZH$^>&m@SD=`TV!A@se?UFDaJSxe>vI!>ZX- z)#3GVARbqRWMa%4pa0|OC}3wZ8Tkw5Stb1ozY1~BMG~Ko;}LZs4NJu3J-ZgGbu}y0 zRj$=n)i3}804t-(1}LzyRpvoGnIiTcC)wY`Tg;b$CgQl1bfg@%3fUE=EV5ncCrbb# z7V)~N^?<1p^$Ob3^m{hHdF+c)P!wfc8q_I`qGtsaG2X3tG~FUoB-C8k3n=YWd1c_F ziWzN8NsyH(x=^r- zJF&waH+Y*3!LOIyXi0n1nvn9Dg8)jiF~)O=Lln=3f5-Asii*hi^X5lqOwMs!p?ZZ| z!^HzmnbWUuMZz2c6l-GcV)CYA0?Kpx&##MZ;>mM90Y6Kha-OhX!_FHo1qn;-+G4MX zutytx#AGX&UA}#2Bj)+eJm+L9r#THLe2@G!{#AY-ihr+ z`#u$01G{}cv|2Ve5I3Z-lkLj1&KZ;a(`?e65Kpf4uezyai(!0#ap=$e zG@fWBDT0QLAl{z+D}&Prj^*FAx8iX8L9hb^Y82g=diduk?ObgG*b858QbKR1p0cT1 z0a$}F(}QFVDRDpj@E#{v024>Bfi!4Zhcij`z;#_~ox{ujCNeVFLrV@r!sI`WnoaX% zsfps~PQ995>91gD0B2FieK_?+R|@gv$ECh9DYhsE5v6Q8f5!69ekjH?gWG40E%hk~x0837jc<&7i z2s$Rxg1<`R&`&`qs52@9S!O)V4decuBbtz;@md4Q$IW7pRE-svb1*pt)!Fl_F5aF(Db-Lq z%3WP~y1h8sB)JT6%KoA}`#n_tu}ux!`44mf&gjuOxSy%|y58^G>1;&V-@s-+%CRh^CEEUqz75pjRuda zpTNUC5?vesZ=<{Rg9skvjO_C9hCuX&x<4jzS}GN+IS#m2>5p6226RN-i_y#z2`aaE zKB97{U>zZ)^fN4s;dSgVt?V#)$SSPc%UC@?;eOeR?CrnHIq(5Zy;;h5yh)k;Nd-^J zM-N3zUQQ-QMgNecdPiTbg0)}$fG|8lC-oWFa*JNK2_tel5EbeA(X-eoA zrv&~5z5Lb8c|B$5J!x?Vwd*u%cZ^WH3r8SZgmyx(rurwhg)T=TLZTKkxZ@9mt&Pl< zv(fn1?mCc2_{bQE5=H#>Q0k`#KF<7hBo)pj*vB_7e?8U-9ea$*8#pF+vE^}kKYEv` z%I#rf76&TxeD>j5{XOFCp$Hi{^ke&iX4fEn4Tb;iQtE3|^HnmtK6~T7Hr5|Tht!w( zo67Q?XiIi*V#ZrjM>83$_jswKf&o^`fx~~^rC`rU@&KX)YNHz8PfC74?O!}+u;ek_fbCtL3hOrtcpVCAiwJNL zr9lh}q#5a|pEB#On9P-Ca~9P%kfxlM!bTCnQM8nCCeg z1`|^DO5EvS>otevnOnfKoWXbAt*iLg5=r>eC}6b~UB=(RV?p)W7ZtI+3ZkYT>QyI) zI_mwRO+T@axZauhde^e>RUjiw8Sd(C9QhJPkCz*x*gyFhQSN1+@kc;;t>m~k`>Y&4 zi(&+}exG1Bo#+bJq{bLj8p=Y&&_F%ih^{m7lH=Oo@=S60azzCm0;Ilb$)Xf>XD zp!{IaP0cND5Psz-^)p1+?+M>VKADhBb%F3gRZh~oMc?hx2A7f+TSV#(mNmg_EKNpNo4kUdQ89D^kCX3nBEBRz!RCo@GRz*6S_T>haPEabus%%lqQ2nZh5ab1tPa z`6ltI+N#B}k>oqOVwP+%M`YG~1+^!0JXtT_PC!iX-j(CN<>pOTSZBdWl#0e3M5VC9 zfPRHn;ErS4|FJ4`+we(}^DMoCR}z$cW(GA;lTD{4P=98AAE~IEP@E-7iH?=1i6l2p z9bW1_MgC0TNaPO7IX@KaJq0~NltV{m(&z{a z>9Xb++sz2aX_IX(GhgzP^y=OX;FuhOjIp2} zv+`85cO4n+Kb9pvMt<-llRH_mPU8MwC4A}8zvytssnrJ>!ZPdLN_H}!Jf)k|gua0x zCaiNMLAKw%2A|t52}zkXea3)+;zUrPHK^8UGQ&(VzfvbKe8b3!2^6Q0*!C^2(lXhk zbDh6@G(odM(=DLR@frN4(uU2ScuQeAD_=|6T7b|zq2C=$uP`(_NfEFs2|11!Us-2TZB$`YzBr29-65FS<8F<0nf%Q zmGVf{n*4JJfK0ZoHk*x#x+dRPcckWw?Z`^S2kd+iOxS5pq+k)}ftu>Q-e83ZgQlx+BfE>-}RM{sQ@%BGTkuO0% zB(CIFe|}9zKXi&?iYZ6n*(G5pW(@(mmcyt9h+XyL>%`OL$lvY6H%&m_Xbaitx9S=Q z26bXtwSZ!Y)sCg*;c1~_@`gh$Yoa3n9GVn44d<@4F+)LI8d>C|e;~rFA@2$AFl0+* zmlJR$U3&c_Tv)(tzPkt}W0J4Km?pkpa1r(0>W z%Ufc9qM#wbpiSUSlNGn_brQ@bk6DR7IYPxYfbq}~G=n7ttLZJ{Whnw|(*wvc`(!U= z^;AxgN1=8ZQadUH*zdwPhCMv{PFX!bQzbKNXVj{@F z>D1T|*2n?GtKq--q$3e*x{RdoJC5{MzaWz-1TwscOB9dJ6brmcuT=k*Lr#$i2X`JzHiF)rr!!oa_dihvk0 zNk4x1!WJh?Bdo<$?e4c<>>XKTYG;%XyhZsK9r?=aSS%zz8dS_(^vcOM8x4O<;zPA# zbCIa-c;#dn>G0V%2ysw%jUhqW%f|1p9excOOD@p4#$r?8Xp0KdpMQ4I_aLk^HIbp> zAJE1mgyP|*BVy&{)T1QziEu&Jm&!QPm|7iGcqkc0^qkhBc?6>ZC>0aRYQ+Sl!q-gv zACW&Q&b{gSrs3_N*N6FGFY->(itve9$N>M@!%+2-( ze$>26_p#hfFgPV}Z$@?!S=N!0{l3COXP6|c>mQDLq8wjaB-K}I(my++2hrL@Nnf3f z{(RUy2sU&JY!W$?Tu_4lIfzvp7lPwC*EEj~32I-6InYglowr_KROCxA==PCz+&qmZP$ zCtP}B;73nieRJqa@ZmG{(Fh=STwoT;ga-A< zZ39(lZ+U*2F>l@C(LHHBzMxrVi?m@@2zqTG3mnM6BbR~qD@^VeWx^kP8H!RQWQ+<_ zdO&{-?KgImDEu0r5ggTY1=Vr|RU?Wt3M$$!PkD=t_HpTNN2Ush89fpoBUT$D5ss0H zfL}{319G%63*eY|RgY=-plWT9M5Z_|FLE^US5f#M2rFuz6=)bAdt4iP8h&NQG%T-d z&{2sw!z&u#^+Xv>N|1iL1IN8u!M%B@tal)a;dp;K@aTB)kah5PJ3>8>-?ZUl4Yyej z;-Mq~2C7^K&~yk`vIsct?yyJj=+mdatl-3FnhnwZa<0= zBeDdO%r1+z;Dbf9t!==hzB;6VS)`$>q>*4qtPUg@57T%RVsM9==kvFfBw}UCVbtw9>DEQhTnPAF1Ub1&tl?!d5ZWdi$?2?5{K`~g%69cjs|Ou7LS0AP?wH0o34EoeTa|ddnfwOPCAZk zYOwk}4aHB>$VS}-5@?KO#UHnZy*vnK(I zuP)0O7(h)xucJ%J!cP>K&APYDG>pz1Xv+{;!&%kU2 z!TxTIW0-(*T$gh?n{&PkxAUGU$A=?yf^%<;>yUu^lP>o~HuqK6w-o}mO&{9rRqj7) zJkJEY$a=i!IlQ}I9y~Dfwyqo~hmT^NkA{$+L64us7lG}nlp|!iE@Vk4?7GIEqT{M$WEJshQfbegO0H6DJ)j~b-`W*4*b@6sWiOD4~R3Fh_{1U^2lH+=k(>an< zz7l-h5<=aQ+c~fH)?XbGN_pN(u7kyk_@!H=@H3FTyo6!i2J zjB*wBzRRlV%WL&0IOZz4ZYX+g2wCcfsppD&Z74+&DaRu8l`nLaa(EO&{giX`RSI%d zN^+S}{jM_n#7lEkn>SS3iPUDnszTbT=?rSaMC#-E>eCwpeSTpBxxzF08r!)Vdm9?2 zU4rZPV(WUZ4mUJ!h_t@zYYE-3f^#HPazr2XwbApmu{O1D*3@mY-#FZBQ*7$c5bGZH zXz}Doecs^kLedj3&=bkili1XgCf1iT&{xXSSKHLrA~w)7Ffht9Fx@n;BsR1)Fm%i_ zblo)cBsTIjFbd2w3f(k{B!v*|puc_HOzKAsCAZ3O0;+1{r4A+ZI+2HDwz7HOXK39;pO z1ItH$NyjXURbO+mJWKR^E37Rm>u&t@UW<7pEW9mi8WJ0ZeBS0;OAaBc8v`2wLtBx2 z+tzy^2MLs`*IFXqPHoFh>kVBxo*kLKozbGbC5eNrp@U<-gX@-qCyAr4p<`gaW9XJ+ zB#Be3p;KbMQ|eDuWk03SO{ao<=aMbwauSznLznt|m*y>(b`sZaL)X51*LNhg@GWK2 zE!XLMxA`r%WfJ#wL-*}`_q{FmLlTcqh92Q0Zq@p37bKqF4Lu+8J^yTZK9hJM8+oA@ zcws%dUFECYYfgO3j$oX13XCs%LDv}^L%}`10zX;VvT|l3xZO&gE9+z z0{etRNrOwagUd-ns*OVG3qqQ=L%0-!gC9cr3PRs)hYpj5jT?nc_jz?2InR=YuN#GL z7liL^haZwg>`sRji9}p&N8FG`em9DIEQrhujNo|_i42QEFO0(4iNb?KZ~lz@CKOGv z6HNn&VSvT36vn)DjyCO!5rD;t6vj&I#7Y-N@$|oe7RIUV#A!j|^v;w>Qw zwy*@p!UWeHS5ug%8!R!fFfkOdlNbp}n#4?uWk^chNy>yI=fIK+3h5I1MGGM*)v%QM z!jxuM)a#v;ZdhtxVd}e`)Oga=VOZL9VcPso+A_pz9Fo3Wn7+4@ehA5kH%$Ljm~pj} zaRbTx4vR1%k^5|v`3!+08^h6!GcO9^cw||G##yAnsW?ShG-TNf#@X*)T2V)g(Q9}t zYv|Zk+4BXN)*G1;#<@yGxoTphjq@GF^7V}KGFK~{j6JN4 z3qp4bBFW%*M=YMdAfXj`(ZTNj9{{yLO22s-nU|TFn^}xojhI#W*_^p+m3e9tVISr} zfD)!cWTz;$i<+_4m1?P5WFyo5ilhgGA()maZ@;XZxax25iDeY{z00uamruVdq|5^Lxs9^J)8grV1O3d8r2qLz%^vDC8Dy)HblgzusC|NlffIFp|>5vL|(e$kOUjB zpm;h!N>X}=I{sk3K>A8(Wg2+P00Lo3YN#7L4vu&P$7y!)oS|U4qNjxkv%NyNr5lU1 z2t%g4D=3UMj2g?YM9Z%m%(1qfwfmmIDcY!`B&Ne_2rLAQ!o3p)E7&B?*#vU_nE>j* z4Wxs6RHrWjdLkSHcc`Z!20#%OD;CE3As)OdQX|G@mcosrn}P0SJ)BKK{F{%9#5;r* ziiE&W+-+w>8kQlx8iEnLVHZZ4R)8BKv^)fYAse>2Mi!C*H5C$yWC=|oH~fTB5*x6xV(}UM%lw0+M_nRF)S^_4kADYzxVuhq`a;p0??-;cdTi#%>6Tz$150p!aq4_Lpe4)0XoW? zoCtss(BZnEz`Tr$3Kj&hE5ZYW0UiEl9nhPGtcxDVU=X^?Ukn~Ps=&fAeQY@z)IdbD z8-l|Kt;Pt9QQGW}JX&*_MpS(!U_QT^z?Cu)h$Jo0Y+T2>0nSiNrCa2LjGD`cM87=Z>T?81NbFb&KkP~RrNJQ#bfDy`iKFwLhxnfJ(;oCk-66(4%^ZSJ zNMBmM9)yhiA29L3JYUJ8UH2iv0GLn*1nM2~sSitL_fV3Py=mM3Ap`CJ-0YyomcOF5 zZ8bK+shUpuvAF;IIi&FpnE80z1%gql93KI&R#w$@1eL zfje%Z5X9rA%D|6qm>gVLP>?$=EdQPO)y|zeFDBZV87Rpelz)h93iTPYD5d^VMh*%p z1*@Y#0*FkNI`xXkzz>*GF&uaTQ>jv@^f=Y@wXD{siKLcVWkf*Hw{YXil`G>#1`8A< z=-tb=?_LLh0}FOAx9~p^{PgWK%(!u30-y9rmP`+wzIjUoR?y%CBuvvIW%V~ z0|kMeEN`FUfs{<^OLy9_?c2C>8~!jXn-UVrxTzG#+xNgf&&EmHwJSNbt^Xc%%O$SY zE@0!hH5nKpnDSk}#C7|ryS#bZBa%@Yp==s-fr`dmSv1b%KjdQ&6s2$fDJ0?Nr8W*V zLafAIHzKXXUw{mr_dzhmIZ|Ii9|$u}H@xh^3^&eb*c^ttV40SSgdjsxg%aE>_OdjwVZ9g8m7iQSQUrWa>;-X!=>dOl|O3^vRd z2jz*x^+u+dXsRg?sXsP{s;UO@hG%kD3?v;c$xYZnk3TlrUzA*8=U#)%HtX!O&_*lm zwA5B>?X}outL?VjcH5W_JcKLmxa9IsNIdA8QSQ1t=uitf{#=Ml2s&s;!S22Img|GJ zZ{-n3uLH3lM;vz8QAZqb03lEd0~dU7uX@-q2f+nXDi;Zx$l_E%hH#?CK!+v z;&GrDdqVOgn_N=FNtJM-vLlXIV#FXNb3$?>L;Tb69*1edN`V6v;;~yXvqH1Uv*?Gj z$vgM_Psk(ZtVf`X5rB~u1WA-qL=#o?PXQ~u;YPu5xUq(Q4nY*r9BL2L!5nLBT$oFv z9W+Qy?ey{t-;4dz(@%i((u_OS^a6&gj2)F!u9%==)nQ((;tDHZy_{uM&>b?BE3QZt zi++r_;?*mz#G-Hqm#lU9RFCtkTU~eM<=5?e4W@hkvx2zB1ta)KCJ(@}$90Hn^Ed&3 zAE8KwnP#>9^Vw@~eWHe*{}hr~8VO`6iezOB!GR>MQQ!MzSOgG4^U)XYzHP--6yKv+ z_9pg$q7EyiuNNjrz$2{k2mrk#kQ`A%VeiXUdJf14`4FT#&soqv=tDe~NU(sA#ucOl&p?nt2+$)X4n%}$5=fLz)IBJPV_Bn84*~@^4g$XE zfb|>44O8Sm4DQGykO&obCU=k%<_H_xY1RIfgs3Bem_GvX01Xj(Iw z`|_o~h{u6Bz#)$Y@E4W{fQL9dVIUcxV>!=h8;oh}ASU2M({RKISs;)An()NqMj)1f zbW9H*44FL-60_L(j1xfnM?ndaPt7Hw2q5f$(CE0(fy8s59?7Rc4YL9_Y=IzUTSFJU zCbopE0ej?h1C~r0us5)w4O=PONB$dwx0R4U9PW?;R}L4r!3`uRsJV^fR-%bhB!vK% zFe(U_(v;1e1xHMg8d8xm5KW-s2n0Dw9#CcqRg}<}2B95Za7R`N=x#90I)r5GA)CAb zvjfeW2YIBInCB@pdeaMBQ6f+fP+Ue$hH#BuV?#Ypv=6TB1M4p1_d`#%N>vTS(gPtR zHUK8_j0#H>)^PbG#hPbYjkk*a^9AOwE z634;Y-gefr2a!-}AEcduq^F6Ky%7LYJCM`vkb-eJqCkr1##to;jPQA6K_pZ`i)PSn z|2QqcqJ&-E+z7PZb>To9{^22ARWM3#lx}f`^Og&OlDPsQ!7?-m&pvuL8}yCNkf1vd z5s0ylZ8XRU4T4?++D*Th44``n0$&VLxd>?=EqF{^dL?imC&SSkctZAX96+WJcIU5f^s5uUjE0FHCI3@gOD*EkV6|}RREIo zhooa834T2Qhc*0=4s#Yr9@+pB@;C&)cN>aOf*KUJ^3WYUC5k|RikqPxWT+-6#pnoP ziK{wwD@8SEregvALaSmmziZL0rBN(a+m+QXXBA9ZCzA#uY@iXyQ?f5hu)Tsr#;%*` zOnwui*P?)c7R^9M@Gg^?OK?V~sUh}ZMxhTh=r-6veX+JUd`C4gwgbWdut9`7-Pe8? zjr2kEg0&47^g2S0jYX~mAMp)QS+KXg{e~Hvb!;mcvLLZdj=l*(pKn0p;LwOh9$Cyp z#x*dJDoj#AsB2*4QaFGr{C82LYa=82HYk8! z=lZ7j`d1*Q6~kBW-5&#EHzLCLZF>EfLESFM1L0OOhYOPNg%e!60zpA7b^#4587DrH zy^8&`DB=ELboPi?r_k0}0)jqJ-Ac+$@TxAm_LKCX7A&$7%h^r$y4(Hkc+b1uRm@{a z#N!M)Sf;whrGsFk16}X%!X(hecr+}55QnD%BI5B5D#+{bI4=C*{|<#~0{LH0L9&vM zoaBF$(+*r7i8-4bWh_TI%L9SEOcWsrOKgG@tzgJMS(0WjXGv&8^L<48&KK=}qaEl+{}eppQ$Om2 zCz1w=JF@MVRTrcZRJH#fiY{Hup-~UWlNrCJWO>DbX;2VqwSjvFElWoqfWlZ6=R~_@ zAJlbj3L_!-hHq@wf;d=n`XNHkHDKlz69oqi%drQ#^(-I&3F1L>a-#@3+zByzsua$l%}YRD=} zS93#m6&9i#HDZL1Bw7Y>g9gEI95+D|VqkfAAyD`sRx*Zmb3#ip4&SgG&tM#BEl7M@VA2pG*0SRI%>g5@n}#S-U(k^k^(O8^+yHf&MJ630_*N6PV1u{r-G^mI+*jeZWK-R$=e0eCmh+b5r9f6rg64XNyCx}%zErK8p9(EAy zQCxOeci`5D=0;#dcRc?D( znl0qcQkr=}cC_Gc3uj+F@=Dfa9Q0;y z^%G&5GP>P1!6a zAZ`Bu7*|=9*J7wjF_kCbSD3+-cqOPs!KkuA0E|k4mw}{G`5M!M2DkJNmXKKSxf|H| zCfA??TZeSwRuG{Wnm?Fs@%frUm?MWNK}^J*ym_V3vi^s+I-GQuBjA8=!0ISm$97cY zAqzw)Ti7*>z?c%rhjr+2JHnbpksiCbTPTMd5+|)U_?n7{Xq7Um(v^tZ;T=6lg#`v; z1reA5W`^wgbS9yzanS+G%3l}cg&UI|#^I`yWu1?uWJjd0<2s7}&>bZ=bFF!C}5Z5bK&~jwTQu{y+@w(30;b5bY;_ZP_?ux|RuOr!RUH zoFhE~$PxtjWpAr(eA=ga(UgS2EQmD^Y62La+8JkcsItZqh;bPe0T_YGY>v8t)02Wl z@d4k123zsC&qG)jI1tYE8i+*?NE!mv#)1?37P?9wVK8=)RS*O#D~1?GXcTd;`f?9~ z4YdGoktq-jRITo+E!~j~b=h2%33V>kx@4*h#$iBcsSNEk47v+PB_=mP_lUxYhWd6kNf+6l2Mx1I9RbJg|8D ziLw_S03QE272%i02ekMQD`yIQCKhs zZX=R(pta?fk_XWfZ5b3V`BPyklP|gysvtFnPzrePW)w2F9S}KmbP#t7RZXS0N$F~Q z>$lE=2(Ez!P5=Ys6M?a|OP|Zg954u=0c?+Y8P((r^kUj%p03hN*fdB@nX9B4rHqX3b1~y3BwMUcb1tZlXHxeHNG7x+cKq)Xr z!E39|da%yQLFCLGlC%;{S75?C&bJf@>)UphNggw(b!9{l!Q8LydPYtGM7M+=_m*`j zCk~t>N;Pa@8=7CK4axN^)!tnya92=k{Y_f|7c*}%i z^w|#Jkkwf|4Izv#+5Xf^LxvD?00)diHZ?*3MsQ04<2I%j2NiuW>_`BAU@})Dv^Yxu ziC`5v+X_-+kNh}*^0YCA@Hjk55W<&AoA3x$2BSVaHORNLOWZMco!7YGHFoedUE{?T z=~5*`0D0ii3Bd+$kRTGV26BLvhLNR;R1@?C2sQD>0+9%6Y#a6m0+Nt9a;sEaae$wO zx3Dlq1h5EnL>93#Re5krB7n0ewzr4;r_S=JrZEozhN$jixu$^zm8yY3MX8|?wuW6k zl7ZbBI5prc8Q`F(kP+Re9LqutuYZIqBD5=xf;F-GntxO)*a5*U$gPZsUzGTVzj|<1 zVjtbyAOJKD{s`x9va&@6cW{=-Tt^73(lET9)xQTJLLPPku2m};6bxcwA!TwM-B1ew zvZ`u?n61k$WE{d$3sq5HnT7GEtmSoS-x;6A7EpqQ56HBx5pBZZ3085~_|yqhW>L2n z2_?e{g&k(ZuIwIx>nro4f9x0zp*EeGkz$ilYEv*~Ggn?ieu9<})S22gk$&k%jzNJq zz2FS)APr|LIPsSg^uEb(aTKQz@TZ^_tI+LBai?B!l)dc{H)#r>gE^7|GN1Db3_$`? zfeN7G6@$!bh0M2q;oQy=2$sPaWbkB$dbk4-$pR6{e(RKe>jB2#8uU;NK%kY88&nP; z0>|JQ`Je?(x$=V%2%w=2Nl=zrH1fl9K7rE#kq|ujK=fT{)bRQb4Ma*Ju$;Fg4e0(E z(7pU}1B>-O0-b;9^%KNjygJReX(d^|^}8D_Gf{Q76%Atl561@g9Rvobq=;%L=U@d& zC`TN@0B<3H_1^GGh8S>J-vlCe0=aqO=-6Lpcq1I=0kj|_7Xk)5XAo(BTVa4o9Y71g z(hXpsTRF}h!eAwdH27*S_f8??28|uyfCU5~g;s)Fd4J)_ferM<__eR~Z^8JtE|CAqH>@(K+d^ac2eL%e?>U(ktNM@k%5{oQc=qe>9eoE*5-173&c zk4X8JFZv}U0>NM=5(E*cmHWrAAtuJn2V6m{Wf8R7iPF&hacYWgIbyp z>4asxi6Y{OE50~mh(887WRXWEx#ZE5#5QG>S7y28j@&dkW|?R1G~7JNozL7q3w*ae z1P`t-U7m&Z8E8d|9`F!?3GU3_p@04tVVb9=x@xPhcKCx~#)*cPU4Rj1oG&uenrpAY z78`4|*JitIx83f+Wx403o6R2H#yfAlO|lthJ$3#@QEI6BOi)t)hK?6;5Qwp7;Cig_ zNz(ifX4hyx_r^SP%{S*9Ia;6@XPj?(Nn%40J`a8L(mQ9pb=O~q{X<;qroHx+wT(S@ z-D~aJZ}|W}QR>7OG}LFo(H;Ks|KvTo=gAR&dUxrkr@ngYug5-n?YHL<0o%U^A0Y&| zC%=3hd-t8bpwEqdc%$5J7y6@>FOYfTpRU|+^Y`b!fB*jnU;qU;z&afuZs9|q%X)x2 z0yfZrB|%^M%C)|oAx(1nOG*BSM!)%4&O-Kk9?#&1!3UPmgeOE{3RSp57G4cy34~#; zVAet!axa4E1D=Tpw=VZ}FlfkASK%(`wEp!0(SkKZViJ|OL?$-TiBCk&0bUrx82W&L zP{iWqZs@xlmWXi9Llp6fcRvdzZ&P0!Bhrj`xh%HPjcASF3TN>})0oFZ zW-_tZ%RT+lTftOjG^IIBYF5*l*TiPbnE5vfJ`9@K1ZOzKIZkqx)12r0&^B|{O?0-? zo$rKaJmoo0dKO8Yzhh@T^|?=e{`S+K{{(0_@5!fp2GpPjMQB15x=@BTaiHoGV?!l6 zQHoa7q8G)e-X8iMXJ*u+9|dVhMLJTF25F-cjA%(!x>A<5)TJ+d=}9{ZQ<~P)rZ>fD zPOT}^g6h<#KLu(~g*wzp@-(F`+F??ax>Tk%)u~T~YE-2mNf)va%ZYh2|zSGv~Ku6M<2UiG?HzV_9xe+4X6L1ffN zt|)p$MQmafyI96H_Em>P4`DaOKF3zpvX{kdW;OewBSv;T;*uaods;{~Zq~G?MQv(T zo7v8qN3w{b>}q8@TiVwC*0wBdt#4lYO%dhxw!Z~#aD_YEcly>SyA@n-OG{klHrKh& zg>Ej5ixT8QHbm2nZg#c1UG8@GiP+`aXKyCKnQF+o-bHVE)w^EylJC4HsV;ao^xpQ? z*S`0~Z+@>vUkHjfw4y!gh~%qZ0vFi82S)H?mwR0Q{vqx7fumhH;E#JYyQy*v2=;agKGoV;=X|$3F&g zkcB*CA{W`nM@DjzmAqsoH`&QghH{jpJY_0Z*~(YOa+bBcWiEHw%U=d_n8iG1GMCxR zXGU|H)x2glC;9$7CjJMCbvR%W*V)c@#&dn)EN3s$InRFvbf5)o+dVfpv=7z~oCm#V zMmO5gLM=3&{)^JWDwo5L#&o7Ny=mO~InIVA?`S=wXik^f)Tc&uJtGa;LyNjYq)v6L zWj$+JM|0KV#i6S?1nXM&+Sk7Zb}4T?X+)!k*TF`1vX#ATC=T1fx}LA6nLTZ4SKHbS zbatx)d|hc@+uPp;cer<#?NDzUM8_6)y4AgIc2kYqxi)u)yWMVj*W2Fr2BxR^OxjE1 z+u#2Nc)&~bZZ1OGyz>@#!WF)7hPQ~|gEV-C(A{u~SKQ(kAI-x(8F2_B9OEAcdB_{C zaaVFY(f%G6dCFD3@^zEkmnJth%2_^hn%5lJE^j8x-z;;R_uS_{-!#s-8Shc^9Oy?! zdeZGI^mP*b)kar()TKUk12Y|;PTw`uslIitcb&UdCn(mV-EXg#-Rx)YF4!SjX>OAp z?Qe&B+;6LP0JHtvT9-+l0fPeTOYF^_tjTMzg+zOwf|xuqX|^rg>4JR(1VeDu2WDUXuvPv86B z{?-}^jzkUHuar{*@zrH~YZ_@vs{rAT|_Z)Ei_fxS4z)!jJ1Hj2c zzadG4eVB*d$iIcrnrwiKe=vb(U=>bjzy~ysN9YFJSU^GJzy9;T7CHn@&;#ZZmBwfT zkD(LUppAd%0Ag?lDj<^Rm=10cz!Ld{r6HJkSb|r%L47b7Cn_6iz>$Bbz}y*uWEh7r zAVHFdz;RfNk??@e2^+2fgS1$L(80oP00SU6k<$Sk(Fudd0f{JlhOhw#X~+zaxI!$% z!U=4Z2z0}{kU)khLI|A0PN~BS%)kv4H23pBJ({5+X#t^-i9$3)3w#lzSPG^7u!KiY zfhBa25F9}yDF8fh11&HK#At&gFhLa5kMbzGzk?1i57gp5CIP1Co#!jK4rXBY=?fQD`inS^TR&`#3N~eIgo>i6bd+?NI5vi z^T>%O&=92X1WN!(OGpY%UT z5I_)zX%IsL2n4;9k#0yskT?M_kVn3xm4Flp$;^-|l*>2)$U8KTBcz6%d=a!XjD%ds zKkGn;{J=O_iI(6HJct9m+z+Iv3y}x`z9`8Q;ft0Ok=rbh#&82q)QUQAgMJB;()hvA zFh!Fz$4bJpT7irIw5Y5rNvxXc$9SDYNLB2pdlGN-F)^tt%fK8?3i`YC7 z-5e1YC6Uc&8j)xLIDpBTbcfecq%a&Zv|>Nqe@< zlQ^F-1V_=)IUxW>KnH=52Pn8f9R!I=&;0`Xj$Djf;&Oal`*hey#0EEO1fAOrFM zgmZ8deUOI%y$LrB(>MhQTpSokQJV^cgmK7%vm6Hb{tO8apao|Loo@I7@<_-NKpSb8 z#*pX$MbO4GtkjR;Ohq76k{AJHpoRz4nr`q-l6cUN$j}gZ1OYvXKp2MuJ&9CRP|cjo zJFGxIScYvROp&-$Z5)Sgz=FMGL$f@cDMUjkL>+Jl106V2M3mI9aa8h%R$diD&q3D3 zj8K%QR&XFpXkUEV8*iw>k?;ewi3Tiy)wVoEOx;FGRaQo2R%b=jOr!;11&4Eu z)Ja{;e=vY_HN!BJ!!fv2(D8;x9f@jnRLL{|ehi&t4T*;>!*MXz>D17~D$QKcfn|Az zXQ3NJaFR9Aqq~6<5*3LIxPywcgPl}aLWG0<>|{+tj00ZujT9vb0?-6cXwDO`giaty zw5S-aP>M(>0+RRxku(b;;EP49O}LN?xR`|{3;|7G1tGYJsi+D)NQeH~3HUAWm-mh23xmFW^zzP>tja zMTInvAT0?&CsSRNxXU5g&cJg3 z0`SgxFi#{Q06z`WEcMcV08=p~Q-U#5kq}Qb;7T_AfCpKcG)N0dz`-B7B}@rgB>+f>-VqT5^~Bne2vt*^)dZE52Gz#~tcB6p1!fqBYgnBR5Qg&o z1!zbWKmcFs-36aGLU8!r^Tn8ufX1%DUTk27dmRLEKwt$H1Livw1O|rm9f=IyU~%YR zB79Z`wbhYW;qPr>DEI(cz}{}~nql2b5Z+b#1&MUj;I9#fUs%SF7+(lRUk&_VuXzS; z7-5?Y35zis^CifCz`$I12I~ceVAVia1!C}ZUppKFVc-T{_ytC_M{Eom>pkCS(9B>o zn>Xf50%%nbeumD>SjX}nm;NaPC&7}H(Sce(2a<&rbU*`SK|YKX5|jlA77z+Mu!ER@ z15zBsIKbqXXbC;A1D(KRJ80dMz*&-j3q1&l5VQ(MD2qw_2akLNN0@|7xP+tC3cm1! zT6Tn#90XX_3rA=Kw}9Gz5CN<0UyvvS5zGqObOd3Z+J6Y z26wQAUH}8|EDyzPMof50kPv~)T?JQQg;GETT}IMFxDHovh3Fg~MsS79eFayD1upD> zODKhip6F9BQXn~9{!1$aD(#d*aFls)0s!y>;GNyt4NCF!%A@GrdCj0L^2-IE?DQj=A%aS;iaR`HD771D%;2XAKRw)~7ct8gp z2i4qPckO@~cuctzfm)o*i434z1J96D9tjiRglTqgSs+E7>z1rZc|-d#Nsz`5M9(j@yzKk-D$4u zX(j&WN_x0P6Yz6OeGoJNf`IvRLLbjlJakel&t$A&+-_{ven7u0RQ^QNR&`gk=0f!r z2}f{YhoR87T+fi$-$H%R01ao8cu)?|RaYgEOO@8@CW%;QR?l3+Vntk1Az)nHn!1Kn zR-tQG7vL9O-%tNz@*siue85MTVUT#sR5`*l-ppjD;ntQ=3%?Qn#df;}iB@g(0BuW9 z4+&9M4ZLP{XEzB??eyjb>|0%Hl4w^OF70ra^()MDRFB85?)H%I)WxXx_r9!+Ws?$+ zl3tJ!jX>mrU}O(8StOxkN@l_T1_A=0gPJu71%J&=V9_)6c&N~XT%OEL(1X$q3I3cN z0joW2lKAjr{)c9sW@Ofcj~@vb{qT|3Wd=2QlrIktkb_NJjk~qfa;`)-2xr8IXA@kV zJZJ;D#f{Bq_3{A4Pz=iCtkTkm^4karDql_*S6nfdPLZ$#>;!YM!1Cr4d+k8;AwB7x zv~nSd^Sfj8KaZ27X5NtK(jBB-J|EITH;UaA4W$uNk?;gvTmtc=hc0-IR`&alIQ-d- zbg|^>2+YqS#BJ{$iE12BaTf{NzQR@~?1rIjN0?#U&d&#Zgl{-iRAs}|VQvyp)NS9tIcINBr#ly}ZnSK!IA=1$LBAPu+J*N9Ogd{t*hL^m8?g z~@U*y#mF9sJPR9IqJHzx@eQmkn4BF2mwH*)Og@gvBPB1e)e zY4Rk>lqy%UZ0YhP%$PD~(yVFoCeEBRd+_M#^CwVA@y_Tl7S9Y&9l_GYi}%jUq^45Q zEFr{{3L>Xed|Hjyg@;n6L!kgI>$8W>l6m4>F-U+;93>Rr#{GvUj-4SF?>;$6Vw0>N z7ykw(EO?>eg(nnAvb2d)t04ajanh5q5+@@RDep1(K@%$>202sy?BJ)7rB0kUbvB4u zv%$oO0UsVo0FRq020P4YlVo8BI&S3hW+NH+9OHJ~Souew&KeePv^c-h=J-fK?%cKO z-hGO5N$#LjG||q?Lhh{kg-K=NO2+2?b#6SLyjI$@7d@8ghpcmD7r$7WD zaF95Ex*6s;&21#+H_%A4%N}^%1Og=D$azr*iae9hLW>4!(?A7TRH|oQf@u({1}P%W zIMHB1pr{6g5X_$6O!~~3jWk-3nR1ozX`g&%^IM(cfYS?`7~Pudu4e|zDnS_~yKJ-n z3~H%CgTM+-G`s)M`w=~=9w|M=?YLepAw>#-QcIj6RETKdPQ;6T&MFXNcn zYN+GrMsK!-nyLu9e3}{vtlfaY?Wh%7d@;ruYrHYX9eeyS$RUd?lUXGfbrcUmQRPz! z=QITmJV!|qb38DgQPou~$HRqBQK11AGc~uv6#g1glw4NGYqj+cZ*e&N^wZ=$=d>M8 zRK%BGv|VIEWdc368rZA3BAE!9tY%3hl0YUIX&Ak>8EXr@{SRn*8Fa{Fm$2f9C2_l! z*+M3K9k$q6inM|oYZ!$17NrR%mj`gTF*)Uw%fKs{p7lihzI?u);Sh*1hDl@u8QDh4`G zi4uep5si2zixeF-=^q9DU_l+I#x1OwL^c&U1mFa>H%CyjnzQ%;0W@)mJsjpQ zk@?5mE)vek_|0pxK)?tzaZZC2f)lPFf&l*38gK%RRhyV*HK}<`lkjFZw{e3M95S>*O;A0<*nnD$l(L^a; z;fj^A)D@cHon%ZRdo^qa1;AN9OxcN7Pp&t8Rn?l916nfsJteYx1c4R zm*RSny5@^eAIt)}1V$}W1s1XVde|B_Vt`l8k+IgjE_So4-R*L>yWZ`HE`Q>)CA4yu zswCxCk`gqY^(2%$C`3Gpc3xEKER{US%F4pa%caqUuo%feGLdT#W|qX63n^P@3Z%7* zlwcFenv_<{{TopC#*ye`2(wFCuG-)OyeE8nnHRU0ghgP)~v6^$HcsK-6Mdx&IiJ2|c1G-afkB z?tVAC{^Kq0dApk@;nji5kkXT=pz^-)C9is2*`-ge$fuHw%+%h5YHv|t^=4rMa?r3WmY*^V^y{?iio-)qi&hYU5?&F<>w~U%R)bbT8I30-T0UnWl)ogqxx?p#>HKu#oABV+@B1 z1Q>R10%puH)Mx!*AAuui-cQz4Dd5&`!%gUNVFOIhI#Zg)QTuk%>V7!l!&&V!a8YmES_}i@6a1?0G@yvEMD=NeC1Z4{4j}<)9Ak zAP@GS5B{J^*qbiF*TO9v&)^rp{e%vz*DJ+CDn$iR7{ym~MLU3l6w1Yd~7$DH1)y+vF#A1;t**}1QB$NUwfS6}s z!YLTR*!{x^NC-@ghJbJj0W3n=(T$Q3z$G|H42pmyYRTKdU4<~u+-X_ek;vVBqfBU3 zC4GP>ObHs)S(oiry#5&w%8`E!#09Pj z#EgR?eToRumA3H}LWavOj0?EITkfS_UWrf$2#~Ts14zQkn^@mVz}`53BsAO*n;^tE zL<&f@B)mWhN`j;`^c^O-V6KGZI2;8^9zsqE zFmyw}NR|K zXdL1~yyqjtr*m}YcZMe`OruHISmMOsZaj`#;6~yE(;K+KoT%7!EQEr(UXE>tcX)@? zw1+!bLoYakJ4i!E{ezGR1TBi_;(bJXoC1uVf+^h2Dj;b0(CA8GLMa>%e=wO_*p4Zz z6e|ehG#)}JoPsO70)WgYD&%O}aif=2#FcqOmW`wSh?Ju_#spMZk_Q-rjL<_h0D<_t zBjTY6{pdqJup#3OiA8iNJ{(Cttiu-o0Q(uko9aV7Xu|{etVlx>h)Dro_Q@p;APmeha!nqFz9Jkti@g|#%8R>sssXrR|$}kzWIbuIN=h;m&2`6D}}6kfrU|+1In;$e31oM zAcAim&8&5d7aD{CtUxkRjUN$!9vo*J0F$c{KprqB9JJVUhK(n%LLWStb0k9FtS3CV z1tsLo9(WUW=%Lwg0xNJ6gFJ>Gwj6|T!ZxzX)II_c_-EKchTZ7l)Ou&tqD186hDCT{ z9fp;D&|)o?M117VDZGLT zP^>|S04vPsOzDC0^=R-!E$C)$5&k420R-br9ft>e?(LvL9zZQlbfZw=9hHWol}b-J zRuArG66ayb#ttv>7Vo>IiKaao!;l+xFUYeG(T|FZ{;u$Jp-eW@+v^&+d}r-TAKlW|H?1YDR#7 zE!+YD7cc<_pZq4U0xxh#5Zj~O8V+8Uo){kkSFi@;zws8Tu^dBj4#(XNb3_m4FP8Fe|N4(W0_6A1u^LaxzvvgMn079vP%dA+}tV*o4;xaNPvobI9N^tTgKQlD{ zL?|;eHTz&GqsS>^neM7G5X(e1S2H+=vpA14IhV6JpYs|=vpPTX`=YZuXV*2G<2Up0 zHtTUWdvhzlvp(s( zMPD>VXS7Cd^z=IOL4R~vOfyH1^ozu^h1@Y7*KpE#`r)% zgS0{SfJoc4Pp8B;H}pKyb1K{O{?>#`|FlvsHB&dWQ$O`Ztjlm$-?a zIEtsZimy0}x44VHIE=@3OL+KEfjD8`IF9GIj_){+_qdO1ER9ExjsG~27rBuiIg%&2 zk}vs$1Udfn3^|iWxs*>il~=iyU-?-(x!oBymUp?Ae>s?kxtNc6HEa2m9ygh%xtgyz zo42`}zqx3dIqz~goY%RX-#MP=xt{Mi#>lz;E;pYCx}Xm_p%=QL^SPgY2$UZ>qc^&v zKRTpG`dlaa57W7%Upl5|x~6YBr~fmhFM6kkx~Pvjsh7H`Gw`RQ`KhlutGBwVzdEeL zTdG@ntk=4&-#V`6x~^NytONS4|2nV-yRZ*Cv0p^5*LATcyRt7kvp2h>A3OCpyR=U` zwO6~fdpWdk^|fz1w|Bd@f4f*``(A@PxtF`SpF6q(w77?Ly0^Q#zdO9gyCAQ7KFhnk z-~Kzk=excSFTGRrzW+PG2fV-!ym$3GN#Fp&C%nQhJi|AM{YqFp z*MB|OhrQTez1NSu*`Gbyr#-rtz1p|E+rK^B3p?A#J>A#6-QPW@&pqDnJ>U1e-`ly~ z|GnT3KH(QWlLx-xC%)n@KI8K>;x|6zN514weoQ|;dP_JMKFH+0?+3r|4?pqK z1n?I>@+ZIYFTX|{Kl497^hZDMJHPZ-zx7{#=~F-UZ$I~UKjUkE#*8~Y*EFJy87Qg{^!3yv;RQ5|3wHjKsa#lk6=NA2N5PzC{SQQ zh65EwO!&|sMTr+NKGbLtV@Ho4L537Ll4MDfCsC$Uxsqi|moH()lsS`TO`A7y=G3{9 zXHTC$fd&;ilxR_-N0BB~x|C^Cr%$0ql{yupj29U!P>`^dYgeyaH)`De8FnPce`aB# zU8$C4*o{=-#+5slZe6=~@#fXLmv3Lce*p&;JeY7{!-o+kzG)S!)~}Ddx@9=_tw^(M zTVh73xh7}DpFxKfJ(_fB)2C6VR=t{aYuB$~$CeF}aaPE;B}i_l({07c4s|!weUL1{ z;Ks>bPF`H{+26=3J8yoGBii8BqtDj9oqKoh-@%6$Kc0Mf^XJj0=Udw`?ytvlQdGVh z`f&KtE5q+yeSiPu^oPT*H;MRrPQLr5Q%}JK8EnwO2O*45!U-v?(83D|W6!jW$=A{T)ZPQMT7!%)W^dF=kt#~**=55B-q7HfuHDk+YPrpL&{%O*OHfvK zp48S{BmNbkjm^yN<)xW!+UcjEw##Idsjk{;mZQ#Eu9)4M`K69~4v=U=E3VjKK&!0p z+KJIt`@gou4K_v+g|6~Zt?|xV@4fl%J0c&f4qWh*TlU-Vp}5A)YnRw6l)1N(cAH#@ z$z8kFoXxd+;e~YV7)Hs9K3w$CNiW@WOdXha@YPwrw+FdTkDaB&Z(BUG&VL@;Y_(~f z98upNKNRv+Bggn?+=*BEP}!Mp-udUDS5NiU{;6lZhv=~viFU|rpDmGn=3!za68O<4 z4W>BGUf@8+@#3iuhRH?=!xRxm41hm+q#G#I@85r|0arZ%uC8#HIs_;5fIqu*fDUbl z0$fbc4l@v>1H^C#6_WA+Or#N2|!hbBniB;?d7=S<% z5iHS(-v|Q|45GwmG!Y!qa6&<-Sj8%i;V&ovMia;3jVKa?eeQD#59e2y`rQvC`s3gK z02xS<1#p0c3|s+|(gHcefsu`LWFC9|LV`_jf|8W9#3NKN#GM>4flVm@9^9Y>g?IoC zZAbzG9Viq*xPz6gbjKO8sE`Ow@E;Ytpi3xG3SH{b6swrU3qqnn4tlULAr#@MUQ&Yc zp^%wq03%-z0Y3575)$a3s zMlAm!&d-Pd7-G1H9|ajwkp^sfhBWEQn9_nd$bqGcghNZ^kWZ56VGd6?i6lK?37uME zlAK@#5pGflOBf+0LM3WUR`C9YHAwLwD{#XWjD{bmv~?*Mue%ujvN9NKt0U%IE2iW5C9Y?ECT>IUGzb;3fdv5+qyvhu=@czXK}G~olPdv)IHvK;e;6PT zpA|tex^c#WoB#|4{U=})F%B_cv;(y;=RfL*lpj(9ql`^V5!omakdBnO%}o{|Pns_s z1Vaqbh^jka z_>oyg1QECVYAM$HkNyEDf~>J9twPfJ5lx`t2#A5}d*sRz5tI)Nu@u25^nnJo{>85y z2`o~$+0DW5kQO*Rt8pFlLuo9dhX`SWH+B))f1DPd5~1R4LlBH?Ow%Qe7>5-i@`q=5 zG$J<^3;`g)4IU#TxJMaoapMQc<31y~5m~Ntv7BX4mGopfI7d9*L50;JA|CPJLUgb9 z6zy_1CEf)uNyK|rf|z%_HEAzQ?i`amJQX1=fWuPnn_rLYS0VlNs!4JgV1kf3fW}l3nlVl&N_nB z`pCyRfP9cZ{?vLNRj353AB0Cpj3Wz+jj};NKnrI$u^V4Fs~=DB#&JmFx13c(JE@JD z;pVp32N{7fsA1%c4x=0KEwYRWa^;pZG}j4XACVOzZoqgz7)p*v9X$YvCN^7>TZCgY zumI|Rp!2r5jqE=f@s0RKn~bSN$Pd+6P(1o!83N6=K{|`=YDdJ4{|0ZempzVVKYM=^ z7ibt*=m%+EeBAP5XB@u(FhI2PivToSD zo#_ukK-CIS=6IMvWjd$@I?hZlLZAZ;Qn2oUk+SB4WZ(`iwF6ekzQ{Php~2pDWE`Xn zk~ky&gaDfGM1W6ViB9-*4`)3AC3+f(A|T`sJf-AM&0ERy{=*QQ*aPD^F^eIFKohJG z!aoR*2uo1%5_*tg;w$;n+3KC|at?`~*|6b%>{*nxq=Orz+yeT#p$%DJAp!0HUxR@1 zpSX9!l0mXcK_}z{zi0<5y+G)HNWvZb>#r-DUy?*8L=dHb1yc!t2(q5y73hWZ(J`S* zRCt~sn%Jc-hl%+_khK)6Fv4c_^57*!m?ekUhd%C_S71+t04{trJxDFpR*fE7ZJAze z_z*(Y{$T_*?bY_mASl5B^?}p~FxaAu1eC$v2!gSMY}$5V7mO%}3POK!K^%Hv7l8g@ z9LlL6a?AsN0iyn>G=}Tjra|>`>k7n8$b{%3K|HA8nO@uBZ2_X2^>xk1aqwe^$-McNUccF3=L5X$WRX}uEts} z7urw@+h-8~kq&VvmV!$Jbm0ZL;R9t0A+C%NKQIK<3gV&=1hIh`T8s=85e;Lo>11&h z%LBSl#u(y(9pV8Es*V}V%)0UgyOcug3W5a|$qwwG4%RM~6e$jnQ5m^Q5B}_64xAAg zRqrF_4j~#(4+`RdF5wXzK@$)Ne{c#MCt(vVVeklo0L;r1z|j$gY7oMa6Tq<%h5+&& zPXsED2nnJP5NNZ^ixnJU9wqM|Mnd=^Vi1n69eUvzR`QydkADE67n;Extf3dc;0{G%`oakl z+KM1VApBOr6ImdtR;A;KKg&VIG_S0Q?{nP)Gp(;nyngA36*pWUT|-QXkZS{s=f=5S9t5{y`A# zZyvB9074)y=dwY>DYy8j8=CJHm*kRXbPxJ={8MvKh|BFAz_ z+^9%2koCUXQ; z@VCBg9K@m9kc>OWZ5*!QnTiM;ydl?aDA$?{JgZD0mg_tzC_R0!eFP3S$DxOyQ#wO{ zI%)AiA(T6~j9*Fs9pa%EEVK(!MhAQ`>wwW-(yS?n@gF$;U=HBmw+ukLij4r=tROM~ zy;{^Hvhg5HAo2jFMr9$E>hA6cVg$&KAhr?y4k$+xBG2$~&Nv~!>`omMp$Oow11b-_ zj1)(8)b6_SBb*8_2_mW1K;YghkNj*6o{2@>pxDB+7~g;m+CYbrN=rooB@==K#K9fx z>*#6{CZmra3=JiC5+O9<6o`rdE&)*CullYpqc%u~Ou+gSQcwY`AS4X}OCb-)(G*mH zHtjJpn|2uSabuS`IOK+O~OFQYasF&RXh zI^YZ0Oru7t+gMP?@~EK>t~0X;xrWFQ(r`4MY_k5SfvJSB*u>4pCNkbaivvuqT3e#C z_ATHL!sLbyt3WH_mQ^qEt%=M?*T$g+3j%&3Dn6@~1ePI(kW2#ufg5V!#|T0Qx$B46 z6(f)nJAoCr3d%o|wOoNR8Kl!7e)S-7ti_1cAbjju3*xkr>xM)#KH=>j3{IjV3%Nc@ zu^QGO9(H~@^Q`2xhy0Z_BeZ2*)-@({UnC(O&Oirnk?QPay3(P{-oXndLF&p34VHiq zc9sgrEX?YKXyZXcb+*jT018wlM6+wV3SvZS$wYyG0O$bj6e32$YZK}$0l}7qG$9>T za})Fc{tja9MxgQ>)F6zs@gDCV{IMYaLH-}Z79sM?NXu3oITLNwRwO(?4orz4KFJOK z=}Z6Na3_+K*1+`&clFvJ_x@U16JD%CEG)8MvNbJggEGoN%-7?|@Q&?(p0%{;9$i>Rntdr&lx(-FKOzIL_7#O)Cj z4kOY?jlSVKWg^*{h&T}<*f*f3awUp9z^I5nuoW~Yt^W>#qHr3Ah#9^e5UrcM%|*oooc42HI7 znfMp!@o1jzfY>)u{vKAaW^kek3{dFm4Vo8I*u* zzSe*R-awRaZ%s==s;Y{rrU3I|l181e5{v|_4`p|`{8AT4 zC-ooRc8>{nS)vgBfVUu;mmZ$?DFhS$4x)GmP*uz4uM|SoP%ZBCD%BLQnk}%! zK9ruSHq}Yp(l_FA?lZ^NV1Y{5BES^X95KD4Pk5P+Nkh+2ZMm^ z&|?n*q@30CKni4E%L{guxJXlqAPx|9vO2}nxIP%2*x8A6ZeOX;Ak4XBWeVmInwS40ync zJ@lt}02j|-sBgBZX%?!dI%UY1%_RBl9EpGCi;?Ug6y5l{Zpw~n6oCM$rxcHpT%m*X z(UKxT5;$Rxk5v94{&;Q``Ka8s8z<=!1akB2I?qf1t`K;eiECMv_O&{cW@7zB;a(1fZ+_{PWfK>A3|4_uQU@<*AmVzwYf3D7B#hZX?1^j zAzizy;_oYs3DZa-RSn`0U@a=5*-ig}nvZublNYd-7u71do0p)tb=yH03qh@*oRi_4 zZ7bcD6*(I!5I=Cc!5V$vxn7$9ya__wgs_AYLZEX(5X6CEi!cL!3%v!y07i>4OSRSRqM)+|{{R{uXc3gGhz^bbOl};)0PHvzqg^_?x4Xf+d%WfIAY%F;-nXPb`h$(D z2b+wBUH;mJ`Den>S%Kr%*uYiAl35n>a!(2H0MMLD3ib>LgAczFqJ-Sxvs-!o`67L^ zDZML|G7kvCmKQ5g>9Ut20(KdXm;u9>!*Y3CBG&neRx8kQ6o2urWx~=@2^b<)aa|x% zRUiTjST5fI4F4e{KMfp!RGpb29G?Igzw}N2^ieYEUL+eAYMxh9-;Qbf(*yNErMV8VL$SzZ2awKZ7}c}qJJY~ z{~>69{{iArHz*DYItVn*i$Mn_o{cH^VOTaL3m1*6vJj!dg9!~be7OEFqyvEj9WWY~ zC1i+T*phG@gJ9vGXA?yp6bI6#&Ye7a`uqtrsL-KAiyA$OG^x_1Oq)7=3N@#b^95_G28!aI_&~ZbN zz#TUwb*^xOWe#@-9^h!9v!?V>LGHNdl;n;|NJQ?;7{p&5fByOIk_v7eV1f#BJ86Q-5gwHSi+qsr^iPQJt$KMkp>L;KV-!7?FU9EXbjSiI$D)r)Z>^NWdSc z=|OCXwf$Wp0f`WS=q9Zo`;Q2y-Bu!MvlRUPk0C4nw#SLJb#9_=k8GmHiNC4#CMUz0 z$hqg7ATIafpL*wp9d0mzBJKIE@CF+~0GDNFyA@Ob}Iqk^|>Ky|O9kOb&z+8bG0sD`m+|a>JklpO>RCm8nD} zXkZ2P!MwzM;)L@s1_vbw!(9a`U&(p~%4iq}ag0M6yV!*d#Zb6^Y^y}1nqNc45e;7K z0vO|HR}6htL=%yWZxutA$znAQa6tZJi#kMCvugIJacmJ9IcS&WFu=!O!1;jKBQhyaJ= zp{Hh8Lnms?hDlsv6LSQsHTJ@j=*r;^d-%g34snl}oEQ!N@kLh35>vrA<)&0LL{sw6 zhXV^DAZ#c|XG|j)f})focL7N=(s7y0Y^F1x3C(CqbDGqwrZulQGX#h&EG0lkJj_5A zNt}fWQbU6j?h*#M&;^`#$&1wh6AyEIGc52#0~F|q2&io{h|D}2up|=)I|y_RlaU)8 z;2=;t$RQ5G`^O665YReEGX77Z@q{HVYKcv7;u3q{CjowQiAOkM5}RNJ<4(f~R*ZBL zk#L+IKY*H$Is!E-E5t@05ede9?nI~Q#3jn7FO5cGrmSdPiEJ9uoXQQPA|{pi>C;6s9OeDOO=WQyF#@u5bk=9^}3XCC(D1FhyfoF}}neq7TJ^td)QQW99*4;KNR1_+2$)TO@i^0|+bB;6Jo)cv&{!|fr}?>MiY}MItj%hM++SW(g{@+q8}Mq zDwBm5RnY(g=Cr%3D6)zgV}<1+frSEK=rD`YAa50Kc7k7!F^lcR90+JJ;Bj~Z3`jVU zMNkFd8^Tvbeh@IK0*M9n&9Y&CU7vpW8#ff43MQeWNd!B9U{o!5ek%%rFm!k=?(S7$ zCJMoNk?39>b>Nq%>c$r^CxZ2kx4f&&*e0DQWBXcBmPS4S6TxxI6sp%2b zqCrS+dVMO8VGVnVSE%61{$oX9537e?Vd{OS{&&8MpkV@t@34CGfWHlkiaeM)XsGp* zY7N+08@-lh6&G^{>VyVQ|I8B^#1flNj`EbNeB~@(GrguE@|Med<}|Ol&2NtLoNKpe zVT1nX8RAk;xSW6)gN8>t&e^rE`Q>VaCX6?KK9->;`W<4}=Gb(O^{i_hz8&Aw#WQ}u zjmxa-6Ba~~lA9zD*y0v!Z#&y*6)F={=}23zyWQ`O_q@|Fhnm#8-~SHyzzcrxgcp^Y zt&aG_D}M1}VRYdi5BbQOi0n=JI@oI@c9>CqnuySb6EHx47DOVqQecwVPxwJT1!5am z$iP29;D`oxTk^E8z3p#b&1f}&Q_JwiS#b~i;0u5F#4o<6c+U9bD_{9!GrsemU*^p# z$?})K5s5hN0PVsF^(ge&=~3@)=!<{+HBpJwDQfBy7uO#buX zkN^DNO!`Tpe)U~@ePhRe&;$a+&|J=C4Vu<79RLaFP$c^BT-v686G(v-h;l*ZWug>a z7RZ4e=z$*yf+ccz^+$s9=XfEAf+<)c0;DJYSACi1GAyWqGf0CqXoEM1gE^>!JLq=~ zFoGorgo*bUJV=D(CxeyeBCGX(G6I9oGJr)0g;6MlQ%HqXXoXj}gYp-ITWB=*hlO8Q zcu%+@E_iuOxGP5phH0pVYsiLe=!S0yhiK*lKgfk#*aKb|hk0moXgGaIh;jdChN8lU zD>#V%un>BPh>56(i^zzL=!iKuhjnO&c=(8w7=2=xB4h}5ffy=<7!oRS{)m<+ila!1 zrD%$$h>9(T0CPx(k|>0C$WN&#i_TPtI--dg$BCfgiJyofp*V}b2#mofjKfHb#b}Jj zm~xm%h&fV=IdY4wm5ZOUi@rE{&4`TGh>h8(joZkL-RO<7NR7-mjV z4e5{%36T*Akl}bD6j_nVcp}fpTL0)L<4BDk8Gt@S#zk2=YVJjsF<$&x`SltW3BMQM~r>3lB!36vvAjs;|n0C4WnEYg;M zD4I@4nyQ(Q7MYp`Ntm}eo5g9I$BCTDshsO~o3{y?n75nG!kB}(m6Ye0UMWC@xt!a{ zo!#l3-wB>rrNKO>5u3s zi0ZkXR(X|HDV&4(lKF|C396tA%AgIJp5xhn=2?dR*@*xeprsj=b6KF&shtk$p&tsO zAu6K9xt~htmK3UskJ+2p$(}Fio*3GqBPydaN~1Muqhr~jGAW^(h@#R+oHq)jK`Nv} zN~D#Uqio5aCz_$@D4#{@q)!T^Q7WY+d88)toC(yUJ_?~z%B5ZErC$oB*GQaKYJE$} zq+1%MX{x4c%BF3)hE-~%JBo{0I*~u>rgw^`d8(&-I({y>BxHJyW_qS*%BO{DsE3NE ziTZbb`loUVjdWV4aH^=2N~x7*#fky8e-n8mWwmsi7*Wqe`l!8Z&~*B!CK) zoH;Wi;;OF-tFbDpvr4PAYOA-3tGTMHyUMG*>Z`vBtidX*!%D2hYOKeKtjVgZ%gU_H z>a5QSt8V;Ot=X!r+sduo>aE`juHh=K<4UgOYOd#suIZ|->&mX}>aOp~ zu79GcZ<(U-YOnW-ulcI4`^vBV>aYI_umLNu152<4Yp@54uv(E?)tZ>7sw6l7u@Nh= z6HBobYq1xLu^Fqe8_TgB>#-jTvLP$7BTKR+YqBScvMH;wE6cJi>#{EkvoR~PGfT5I zYqK|tvpK7?JIk{@>$5)#v_UJhLrb(p{%f>Hi?m6rv`fphP3yEz3$;-zwJ$rFpfa!5 zDxC?dwOh-zUF)@93$|e^wqr}SWox!)i?(SStj=<^4r{7!3%7AAw{tsTZ9A=Wi??~J zw|mRCIAgb(+P8r#xPwc$h3kwA8=7uwxQolUjqA9R`nNIqxRXn{m20_R8o8i)xtq(m zo$I*{nz@qtxuZ+ErE9vH8M=0Qx~t2&t?Rl&nYvB-y0c5WwQIZe7`udeySvM~z3aQj zn7h9#yu(Yp#aoKOYrM&;yvxhHUx>WT3%$`Rz0(VV&r7}6i@n*ay~Ss}+v~mG3%=od zcik(#%Rj` zzy)l+`D?%ltiTH#y$H;}4-CN(e7g=T!4+)57u>lNjKLes!5u8P8tlO#EW#r^svu0l zCyc@=tfwZd!Y%B=FD#`j48t=_!!8+yv1Wo#){a*UL1sAe8q?h zodR6OZw$wasD)@8gp@eKVjRbLtjAL5gLEv#Lx{q7jCF~y4-mw1A|yc+6mmtpNGj?Al z;sFS@Uj7JA}~qB z3t?t=mW;_av&knC&pPwZJTuRtoOg3v%3Ns25~Mbg#?X@XN;(5-p~eZ8@CYgp%{G(D zgwsp~pjF0|BJI%zNzlr#%rj|4Ztidf_?#?vCCjwzEcSM9rmzaL@B(>a%gBs%xtt>< z1b%6T2!%{N4E~4?;a4_7P1Jl+%tLd`GLtEV zw1K-KEL^SC5tBt~q)BcBN6-)kYUW~Sq(+qdEFu65VqkLty)y%yB=n+8TK568Fbyh= zGyU909p=fM?8r1m&;@IUgY|C@GI5|pa7%Is8Pak( zts)RG7So70D&Mp^wJFy*eR@H)oL~Z$}kRL0NNse3$qO>c+GNltuqF$ zBMP2#^2IZP&0#@<;5ozKiH&unyx683O^)^{pYbPt^IjqF8&5?u(E&3i4l}ev(gaWk zZcus>kt^r{``xLMHrnX44%*O#b1> zmB?ta$k;6rw2&(A#ofk7DeLlGCc+5bunYIqEqk3Jv9uD|5JAq;2;)FSDKZmuog=ZN zW*rbK;axKoj&c)@GkBgNd|q=7zB3SRUWDB# z{L98(K_JxYzy2d+@Y^UNLeoWD&}Aad4(;970oeY4zHB1pZVb64?kExhvgKT5kN}F@ z1JtDt`Je^_&L=Z;NLg?qNp@g_G!8!YUL=uaJ2cfHBgy;~@eN)nihvTMq72l~ED@nF zIwI$Wt|yPu<|u*?aK0jPo@O}&L^`4oZLLWU&+sQgWvJ3$(i~xYWMewCVkiEx^Aw*W z3C~_prXn9`@$pT|Vl!WHEG!RT5L`Dw@;!r9=k3>Z8@E;}(75@+U zbhTo&kuwVB~GM> zumv18K_=#LqXl6HI&PFmY*ELJn>JY>61d|gibOnaszhYzhDk-769u{BlJnn*U+vtv z^J1c%nTV3yLHUPh=+mV>rBW(oq@tiwuplBNfXGy-Q?Cvp{D3Ky#)u~{l`4fw&sI%r z<(}o5=xeD}Mg&OZ+t=^kzcOBAus}h=VZ@0QBX-c(@nZ*oCI73Wj~*3?l`l``+}S6A zPx?fQriV`7yd?-VQz5}`^yr>GKZJTMy5}L$;P%=6H$qfx{(VSQ1J~EHHSnIZfv4G; z^EjH&ujk$Thg+X+-j$IfeE$2jaD<3=YNtuyx#EXm*_4n_#*O+v`0^wE zc{Wbku3h@02CaV{=_VI(df5e-akyzBq7Z`dZ4quyGSqOxoJL57nh}(lhCqZA z8Rv>c#+m6K{>DH+suWk`3KIJ`q)3o)s1X9E5W9H^rxz0vRU^Va6F} z41@*|=Nko?8Aps4drJl!1uH!A9}#q^kQ)GgHG7IcxQYUAMaGY4xyf7jq!%*fw2C>mW znP~VFXPj1f+_Fn?zQixSS_HN7nj~`S0FrUMDfL+yGZmD&XZ(DcX-hq7EdH5Gr7W zH{3WI4mZ|#kt2^jnj`s$I+$Y(zW>@Owu%N3s-0e**&aNlm;$7iX56u+7cjc*j4G>Y z#RQeHL|AJTS6FEaa{SC9@DQ+EapkL6yoeF5Sj#J15ev3;;1YPLA6VMwH6jUyFos!7 z1rq}q49?6t;2{7gEQ1*cO6Cx|nf`|p0N@AOoF+At`5<>(1D?-B2Ms0!K`}gH9et#M zZU5*1ZRp{V4uobiltIITSmc^{pyfC)yo7BMKmbed#wW))4Hg1$f)ok|hdPW%C5m_^ zBx;0;B?LeskhnrYxP~5M7y<_Xkqz^BhaOodqZ*|kr2LG;M6}8y!j$2o=z&XL2+4BLE0jc_tfQF#LxVA#qs$MJ=(p^{r08wa8mISyz%3L%0J zhej*X%U=d_m~+C$Nbu7nu~8&N1HENIjhM}8tTII&b<{8W=&ENNb6>-#=qI)L$!;RV zo)O9C4g)qYRk<@n@dT4EBO+2_X2}35SP3Ir3dwaQwNdbl)+_as`XgXjxyliIOlefK9!~~pZovSz@iyaa`6P{?_2;S1S(;-A~ zsI7=V9_TqvfQ~Ypxb153mxv;mvIC-frfnrMTG}3RwYQzj3f!;-i4boM?crfaOnQ+u zfHNkD!fr%Z@CG)t0WHLXZfCBC6B3BS9d}Gf`M?*I@VEnn?qMHJG;s>65a1H{H6>QG zl0Uk*Dl4+nZ&f0q2~-?GB2>vk;!L56RpON|5-f}bEi3^HLS|-EBm)joU<}gC_pUuW zjm3_Gn&Av_iG3-=ZkR(6>&!!ap4$!fM&Oz>AY=d&iQ+uiyJAPaFnY8>!!;>#oFV=r z1fw|3NMimxSG5`N|yMTw2~IRs8sNs93i&sAG*&K#M7BkK#1j2>J8BloZM{ zxinG9$Oe>sQqya3@*hgNwnRddYn?P3*2v_co~VOvH0#HqyyMO?LJ4bDYl`0zx#>}H zGXCshi{sc7LASxJjk`KqOxTQ)h_N^3>5Aa{&CU+>IRiRvW>*{2BPV&uO@4Ber(ETH z=|N&kfR1?d0_I&1EMX0sSe$3A2aF9{L>em*>8*_bbclmVDbm>tfwp@8Q2IYK@wnq| zLJy{#NYh6kI@wXgxUki&=}H^A(AJi>T^##?p?*uLQ++ZXkV6~PSAdwv2zVobyBqlJ z0XVFox^72E9@@|n3~^-OfY}LDh5|yTU~i-Dc(28rBI5NKxFYp6K`8<_wxPe1AT)LZopg|i177P)% z9*me^TDXAHK|wnxQ5&n2GLe)*2&h^&ibI$WArM!q2oiik{eX{4lQW$D2qtwRsyKV3 zNy|bJ>?2sZ8#5agI+VaHL_AdaR)FY@c6 z>>D%L+RCo{zOt+i-bl)&EI)n7%8g1tJX*F$#1cGA26$toEHn^_+rI)F%tBnnzEDDY zc@b1nBu%ov0W?5p_>Vm>GMWJaNT`^Kc)(`cB#}EsM`6NZ+ssfK%t|XaC6f>X{Eufy z!7T)nMIp^W{D6*;1~c&%a7oQUX+t<`w;-t|*c=DbJpQU`x&lRk68f-0Vbe{-#Ke!d z&2RV*X0S8BoIlErvnnpgZqqA7uMP#SoEhZ22&DV0fPA&Tag}r=2rG33 zjSzvu;RH*F1W8awcFe7ufJfZo$F0ME_ z>IxpbI|)Di(~-#2f51B}kRH8byE`>6o_G&nID@G)ipGnG$0JF}qliq%JWG%ZO=Szf zSxJqkyiJ{p&6~;U2vxbj1Dza<3gbz|@X5y5ndy_UJ4;GE{RiAzq1A9c^}`9=GdI~n z%jmENVI5X`D37F^ApmenT{TOtBvxT1zKS@G9LtQetjdTuRufWIX$8Kwtg-j_4Z8e? zq})~aa5dS4mc8stKU|UN^u&nK6a@hgdEJF34bhCV#AR}jd^I9X!V%=G2o1#_c$OoSh)Pl=G}FzN z;!QzwSdHBUjR+AJ5rm+6gj`b+?QGCPd{6xRhmj>6HjF|FwZucwLX@r9^=wyW`_3&4 z&-r|piICW>@Q^hmPd5bEp1RqHfltmPL#*nfN2;FbgilS{*^>27(F9Pd1p=p~SfjHQ zXb6`df!CF-*GN&$2Nlh<)shJfOiS#Ot1}p^S>yrDKK`#RHc1tKsdfiP1zt=-$r zUEN)}zmOi07|5fXyMMTdHb7m})d)mQ0Ow)?xN|$_s@c9cFNSM|Gq@W_1xd#n$qYfs zMtLxehzkSqAO3X(tMF6-qo4lu9|S^((2EGF5a0oJ$J5(MR=prs)tMX1Axs-riQux_ z%T=c1y{J5mqohj3yH%W^vWUpCr{rJ_#+$a3KI#)c?DMe_W=jeNVR7YPf&*6qv0xif z&u6#>9ay#%j6ZIhwV#a`$1L1)v&3~vr#4wQO?tPfR4IhznKR7J{M^t=Vo+ic&Cf(H zWym(DHK|goVx0Ju>;A;G8S7c0wc;+uiDi-xNYE%jIFd#X;z;SvlYNo(MB|;3&LU=; z|2$bMUSm;d!&9RO{x~+JRbm&6)-P_3Fm7Z)?!dBTx<{5X4h+B_S=y1v!=|N?n*duu z>T-76vG3To_&3`iR`gWb-M-Ow2w zfDAi|D1lA*I)D`3b|l{0vPT}GN56TXO7H~yc~j{1$0!|=xIfn9iSPhoaEC|zNQCIf_<}snuwU8A$uO%__hCtw^oaweNt-lG2Y%qg zaMj4j8Q7i4fDo2@Abq!=w*yxX>+*Lo>s;-K;oqs8~8nwwx#`_r$7dGFc4G&%n+=_8bQ+ zNCu0lrY{%9P^X1KXm++4)%#a?^| ztl1i9tSf26#yIe6if{qQ+97i04B1t!*cvWY*v8|UQrHq+c6J5IeA9V$(|F`9O~BFv zGl=LpoPG}R2Zx#OS_3`k2nV#TgLVTPoCxQ_f;ng&LZyR+w#Y@T2&I5Lr-;;zrc~w< z*HTag*}+LX0GvQ$zfe1Z(;psspJ%FwlNPX;RK=BMX_uy%MhJr(fB`=^2hm`Uk!zy4 zbOH|k7zEd7N)0}-7njSqJc0zMp&LR=nzoEC2!JT_gJ(z$?JxrX@Bk0$GBp2)6e@$2 zKmlaf$m$b~5<&n!DD&8WY8DEPJ@50)*j5_W%G2-y32-AgIuEYCR<9;Au!awBaE5nj zY$WLb>OO09n-c^)0Wc6|5yX)kX%0_1*J%*Fk5B~9`dcEQ1uKdOR~9D41cPlf0cLm? zUe7;N`i6;(2o%5tKrUNU(vj$?g$JRJl9Ct+Va;r7Kch2%K*+2Pm;py?k&1{AL;99M z6fM8a!f9Wn4zTu?iUvD_4{1;aEtWMy!l}O8<6ua4aPec(-XXMV4nWAP?lSP`sbA5L5V)shN*A7L^;chRn$Icq3D29?_KaWnfv>lRk}5@bkc201 zsh@hPuX>oh{nqcPac^g?T=~XkUW~=QqB+EE2XILHw4a9e)|&sOs{n5SIe>#1 z8y+(u03&cTvS}UxCvd+YM*{c*+vys=QD*{3go9w`R*)Sn1ychit>!R<_IX~3pl5vA zgh!CN%!sYo(QwL#EyG8A4FMiIu!$899y|rFRU&{q@T~5|h&On78<&Himi`Qh)(N4w z4?qwKAOD9$$Z`4-X%LWv``H&v;IFhuX#n<|toS7YScGSaiw7#OJa9A-Q0JAK@&~T+ znb8jNxNy7Tbn@_^4DK>6gAJ*;>Fsdz8U_v&Q~%MJ2M`;g^XHA)1CHN|R@ab+G9Qf> zvuc3g@^9cj6a4fc)HlzVK6+ICkvN!!;J-r%1^y#oDPcl1K|YAm=aHbGefrE$OsR6E z$`8Y`DN$+S**KU41Bn|aZPzZEVh|8~)C~&75y_|l88~xXG+w)ajT^&qD%Gl1uVT$= z)hJSeB9e`>LUGZ!DilKq8#fLdw6;0T{v!)lE8Ucj)V76|6I+sj{(;nf%RBCy*nwmN z=Y=M%Z{5a@A5#oOb6nn>UH^JXN#HKHUb}ud8=CQMH(sEnEwlOWLol7Yunbfr4&2V3 zVOJ0H<{7l;m4`h)lcub<%x6-^mL0fx?e5S-2Zm@pEeOFzm`pY#iafAuZJWlX; zPvO0VJ|D<6>FTuKJVP}bKBe5L^MhjlXY{&LsC;jZlNmwl5#*9;nvrIjGpN~QU`*f{ zH`NDe>GqU3#LYBKGNsuz9YF*@;u;vZ9Vi=ZpE(mCgyI>NTwH?zXWny|O{E=o-W9f7 zX{V(Y(_ghg^^;OGHT6_fHFmSzPThUymx>=I$fIiL6?GK;d;4+7jA-|b=OScZf*Gcm zW0F~>nP;MzrkZQA*`}Ls!Wk!+KJZYdop;)4Og!-L*=IcQpuqzne|CW<4>~Xt4;OY4 zl1@BS)Y&K+xuAo_5?4j$vI#3hWTXivtT4MtB%8EC-D=WavaGa5 z3{;4+kw9w*CwovtfF_+>QUpaN3>%3fny`{Ay5WusEdj}PTkW%9=HZ4LYb20T2Xwgc zAOUy?yoRc8xRKt$ZnUB8KLNWD#g~G(<4!vtgG~Mu0f=;LO)t*4BTYLf4J3&>>6y@u zJ0_=B7bT^flJhC1lv2tn&{8Bw&#SoN3MQrCG0+E?q@t}xrI=DQE2W?UbVWG3@(Oh% z@Qg~)N5k?%sBFscLIxI4Ac5Lzv)%UE4!|9^+zzZM2s$R|BM%k@k1eJL#+XD;H9&l# z&=Lbx($GT#iExZb`DkI42a(u=P(9WlQPD*zHI8>Z?Cd?zALk$>4-|8C3Wkwg?(^gBg>oP)RCtA#YuKqZ|-rh_Xn zQqzNs(~=WyPW1i{l>=HyPzc?t6%80-_x|1fR^^XJjm0m{HIM-)bf?z$eAoBp3BTxN zE$U<6KR`f>0FJ{OV8~pT)Z#$xxvxzhpv5zC!W1wF!ASz5hyegmLFOERARS-?G!pm) zqa5f2Pyq*Q2I7bO?JpyW_#imKz`&IlV1{c6Ll1_LpB2%^OC6+PFG5HW>um5JfDng= zkOT$EtVJ44cvpltsKXle@I2Td&_FuiLA7+_3u5C^i|UsWGLDZT9iSi>!N493R;epW zd&%|;MnV&!FecXn;#3GQ!<;B^2vi)y`En9Q0zyX^Imyf(2ckmnNy7<)3?f`|NE0mj za7Itc&QPF8n<ib4rIy$7<%qT4kyg)B#ti0$)b-z^XXF>V_G~!>r&iKdtnN z6AI;oCs@%3S1KnFmQZN7h#&yvP-0lc>Q%Nrln7}}YYzl8#IRZ+NORqbAm);VD@`y_ zPCSAL_M!+$IbqU>zOS@ zpHqNAn4@FuctXrr_79i-)oc(la|aV1bF-fH>=dt1LHNAF3ZD5)4*^gb(=0}SM8H+8 zMc8RZBr!B?dEl)*Lq#4SO3$m5?QCgt8)Mlvx5plKnH>m)NDdm=%VIXOn%yjCJL}oc zf;O~q0;Xt7YueMIHnpl7xiU%iPhH>H14sK#f zQw0{(I2kof1K<8>!OVS!7@W%kPysl^A|A1I&4yj=GM2k?Bk@2Gp$#WsfB-FsMBt>5 z73y&$A;4oMj&rO$76UoRLLM@aSBu{wBRR=RUNV!H3|kLFIm%L&GIX14h>FT)MU zARtENEQh&0BRbKFUbLdw%;-l$I?|GkuYm7t=}UiV(wg3MG5hRi#{!zuq8>G=OKs{? zqdL{9UNvf;jOka~xyr1bHLa)O=})T-&~&RcuY2w5UjsYX!X7rU`|AK#!#dd>?!m5$ z?QBWk8vfV1em1qMZS8AgJKNgc_OL6h>~D7p#M~Y?nxp;cX_GtM>Rvay+wJam!yC0e zKsLC)-2-vQJKrNVx1Z6i?|%b4-~t~w!3$3AdDmOn_cl1g<;(9b`@7*1pE$)UZt;s_ zJje)`H@y=M^{<0H>|!4~ z*~@PBP;vdwUOzkA+wS(a!#(bD=Xu(_y!N@{J@0zoJKy{6cVO2Y)L;HP;R|p0!y`WN z{)!KB;NN!lQ0Y4IlAk=~D{uMBV?OhmUp(hK@A=PzKJ;a7e6An=Ce5Qh^{QVz>svp0 z(ZfFWvY$QeYhPl;;@a_pkCW?r@B7~aKlos_J@Jcg{Np1(`C%gcc1^z~;X^u>-2^&bo3U=C8=4Dw+95BlH_ z0->DLV0S&A4jSPRDqs*Q;Sw@o6FMP54B?6uArev{5@!eJcBq2i@sv!zKJ;^79);U4l~ANpb0)!_~1;UNCs z9~$BzB4Q!}T_CO@Av)stDPkl_;v`a{#W7-C;6Ns7;wExpCwk&1f?_C&;wX}0DVpLb zqGBql;wrLYE4tzf=80V?QEVJ(>wV3gqwkV?i3^K_VoQ0b~jeWJ9uE zLPBIjO5{Yw*Futs8_u6Yf*(b4WJh}BM{<@$j)^<|gXNWEm_*-7?jTRD9c1lfLl$LJO661z zg8UdBUyflS*l?Y4n$TGp-#@_?x`e2NTFgbrePB1@c!LpV-Dt7+9mn%Ql@D3o@T;kY|5q?@!WJ=QKeRk*coo9dg=YOJK zde+5y`ebmrXKyMXaB8P@GN@{@rGjqfa1tnhQfP%*DD4R-T}BpakP>O! z!Dv>huY|mdSQx&riymyoYHBX(j86Kr1y=a@9}7R&M0?M>Ff3BZUQP?La2{^=9A{6 zoib{pI%>;(=~etB9-8TRKI)}nYNj$=q*}$KvSFoKs-}wSsFLcsacWg~Y8Zm*=9Oxz zy6USs+o@7Ts#@Wo@+qw1YOd<4s>$k9(CQJcYL@Qmuo7#r&K#cRo}MD*uQID|8tbz{ zYqXwOuhwU?TB{mLYqo0ZwhkG!7U;D$E4PaN>$sBZet9dKg6prA>$LgmA1EWJjo#KIcI8mq+yE5&z$dW9>o@~r&>#?b9!ZBO1 zitNa~Y#YjK&q`~@)oilK+OFp8PbR`X%mWknUh;4O@ZC-t$Y^;k(KNgfUC>CITrDv* zlF$BU$_DMXvFxuM0w?If!G6}kw1LKKR#$ZqF}TCxAk)$`4T%*kBJ6`1%+2ULjv64r zAQsN=EbT=&twcm^bh*Sh$WKeqk)HnT#~7F`gm?)-@Dc(IP>VcJH@Fd-h`<2>h7C~! zFVRpWHP8bc32Whx1u2kWRmId+tyX01n!K(sZ7tWHr`LWh*fvuO%!)qoF0Ry3s3g=v zae^g0f(kq?oQ!SR(o+F2OysOYPqjf3psm_Q5XVqe$DBdyzRSwA?c18kS)r9Ftimk3 z0A0wfW%-xLiU4JugxuI|jwNj#+HL$gKnFkqI^b>UG40^uPI4+mGt9%`pw{8i(i|N~ zob3lUfYM4hf?Al7Csl@v97Y8HN1DjSV7P^sECpM%Md`i>V&p{_tk!Z+hFknlT}%+w zVnytx2@AW=?9y&|0`2VzZT{}gfgIp)LFKR<>`GnuQ>{Qoul!1|49m1w4Vx5#C4dW> zB=HiX$y2R?VXc50keM-AFUWjM8E7wfa4$;$f;qsgS>cS%z!lP{?n<0*Rvbdrz*oYa zz%g({{yKm`D1;MmfFzhi8YDnC-i{n=-v0jH)B5i}Bu)UQmP>epbEE_eyO~jV!!sli zMU=oZlu-SU1vX#-Uv+>Y0CE2WRrWY8noR8t89*Qa@mF+%QaRFyl+ko4!Z?V*8+AY} zEbwD+kyW_xndEZcFmenpr|k}`4g^DoC0y7p)2Nh6nBW1ed`Yjc)LA6UzqCoRII)>f zvzkl{^?pblxPgrw{u9USm{qVXOOQ+$JI2ja^8hFU&&0y6vj&Y!83>fRe&^Wfe1H{beY_8RqS$^)O4s!v@gr%40nnS zXv#Z~Q=K8gpDaqf4Q(?avzRP15I=@9LsYUnGn-s9noMVkJx^D{Qo@T-PyUL)D2T)!!;K-BMANPy0WbnO3{T+%Fx;RH z72Hma={2DK`O)2!P82u*)0#l@C`1*^4PX0pLy*A_%nkE6k7Osb18fBDV73E84MuIEWuCLlpUTAtg}et`a2qwobp~Geub;{0VP4mk!hdIux~_R0@REn>!yfv_mlvm}e~ z{tHU|gSpIt$b|sCWYAsZlpe58lrNMt|AUYdIV*@1WMnlP98|zKuTW7$3ve^VWP#Wa zz#U`^MKH`<)VUkPG+lJ{RQ%6adBInM)jyET_0|ksjI~M#P1t-0BJhk_Sxs6y($P?j zmk)#{pjFSzZAReL&=>(<-QI~MD+-cHT*FNX5VYLTaX~xaKj#=d_;p}Az+e|PM;x|_ zeE|F7`fBTgH5^1jI7Hm+x{swnXcNFC_!!+S=8VCX0yl<>iL246jpfN2W`~KK# zhn9nocXu1`_N26H*hbyAtKZN&m%c&&LGy1c$WHgun<-L<3R$MHmD_ zQ^Y$h4T5)g1~b6&vEameJVkVWyf9?RI8<d+2Ve*g-y(iXPYjtHcWWUPX_qgt_PeL5yv`NQ?49gs&`1v$PAc{xHG55(Sk3)!-GRS*C-Q}@J>w447! z0X&r(sESXm0Z_2H2+hI3(Ee68#-Fc*Sb0n@JcG=1@mIZ!67@ne%uFwUK@TgsS)(x~ zuyJ@F4be;uDl9pr9|G360y_f*BN$C7yn-vlLQ-_VCHRahOwB2rwP@vRT$d#EE(r)&lK9Y~ zzIj*zfN(^{0SSKk9F`b4kI6$5Bk6gO07B9}jY>j}bm-vUKZE-Ixa9m-N#hcqLx~nO zdK76=rAvu^7?w@RKSkrjAjs(yYR(bLx<&EX0n)fIJ|B(inv+0TFKEkR@oC~2H6Tps z*0p;VZ_*LTxaCEing;*RL+LD9m0`R8 z&#<)!fW#ZGateWn(G*;e!3G_C5W)y0oRGo_ExZuJ3^o4Tki!l={1C(tMdYvt9!xwD z#S}|GC!Std>>|Y&P1J#zc({-e#~gKxv4;_v$|H`&{^4K_IPNn5$o&YwBaSD0${@j< zm~=8U5;l>=x1vnoWEQKolm$HkH1Wg;t@Oz9A453N12dgC0n?~TI4RR7HTTF#PC9cM zvxuA$DWc5|G+`ympXMCu%P_~(Gp`lg*kY&^-dKaZ4(Myek6Pj_y44))%hqjy$afgc0C@F=}p)@(AOad-JwoO4w zfyK8EFi8bI6P~s1A7zR4NfT6#7;4x&h$F!iRsK5jkwF<=$iM;$BzPCzc;$unp?d9o z$U;G6%2lU}>J^Ekdj%et;DQZ4*ntj0(FlrvPg>$3py;9EA%M+5*nvW1O5+ECcS1vm zhej9)jUXIYNuP@!Qdl1}fH0}$knaUJ4VEXa*Asniu9uRFXdY^(SAPl$-3GCWs)N!N zJ31%7_6sbSto~68(et<)gSgmcD|*D-UXBY*LB`o{wMNFVVyV?Q@MD>?C+t9x&AP#Y zy`k712b?g_^U1K_{N9@D20egeGijU{>W^o{xM~D$ z#P#EW@%!+xhoX8DdsH4sA^RVy14RM?0q78iOciBHqOlT|V09(mXyOumddnu*)IbJ~ zU{Kg9N}h&dr<@dmOT-a@CKwo}L*eO!dx{{UDA=aFct8$qK+Zo3fCo1?<{y^A6b)^N zw(xK&hjfr(PV&%(JGh}&d2oXd^U^AJJQ0e)`p2(?mXlWT>mR%7)f9mxtZf-(SY@(S zEVA|wOL*%!(}K^j7&XQ=b?aOHIUzwQRtOlXS5AUvFPdQt zk%>$s%x+SzPXr}mI-^)kQid^$?Cf4UDMP&oz>s-B%pjO_K+Zl{NP*ETBV4GL2u9H| zAlQqMluQHAB;pgGd@_%)drGF@k~*d(EgQqz&Fjj-#D93rcgFM`4s+QcWI7^rITS|f zR`ZB&7$_-y^V>MS;g$-4fEJtaPf_6Z3~qWeG2Dd4I-wDb|1=XRA_&H2=JSu$P0l=3 zXhbmoVIJ7nrgFN;$y9Vl6y6c#5w+tB=ZTd%XhO)4MF7JQYD&PUdYQTRm$qpJE zN&>6oKnwn0ff9{ttrQ^%OKgG@t%!`8^ner&)`=5$dd{HE<-xD`p%Iqo1SbM(%M{Ml zCy>p{ryLW&A?8GfNSgy27#cv%o^^@Ys>2#erG;$W0ac)JVmnyN+9_m9i$pO&JFpmA zUv*J5c#PAwbn6p5N{d?mm?N}?GTbb|u}-DP15pg2T$}E(U4N9W1OizheWffXhLA}W ze@49PmVqdb#Qx>bCML&`U@4RBhq+&_4;&27S_r*%Q5ZLFdh*sR7d%JhsnrLl`W z*zuc$i!cBcVNMLMCc~-=hG}M)P}(RWm?TAv*n9|32@CWeW)t!5#)09S*tnnOv~X~; zftNVYnE-8P0K* z^PK5iXFK0HdOziX{NP837XkHsCL%-_uv()V37UO8*xnfc-MvKvn$h_KYmhKIhd5js z)0n0W{sHQ6X*(QltxZy(TeY;N+TfM3sb;JzA~Qn9S|SpX$m?Dkdz8Osx7LQDQwG_K z*GsU%5thjHtxbSxRj)ducX8CH{^6)qVfIo{@ckc9-h0d=uX#< z)uqsN`NeS2ows<`eRfXXTj25r7`samuas%Y6ZaA=zV)!j$Vpybjog0m&H9oYCdi#kc&CxFI)K;+%US);X{DCiKUD~ z@p6?f`i$QB1nlRc9Z0D>6eJs_phGD$Kv(AtUd)~7Cmr*d*Zk%=-+9k} z{&;$}>Jkc#JwgB3dxnl*q6-m^uI?Ef@1Vj|;b$WE5w&`gUQ7VtUU^@c((VxW+#B_;0WXVc;)wN$!n5JA8yGt1C(g!)=3L>;gPJ-Qj zayIe&=tF<{p$Q9W06y>|1MUPkiV12+h8%^N@S;_!2sfJH3^M)--E>9W zZmZou2B5)`qrU1>0Bo1L?vP1+(rf&MF zzhdr=X6}}H(B^ijYOX;CxNxO}$tQ&D$W{!QLJahju06Z}E=;QHj;TM^t0-WsL(0s- z9D@$y44Ftk8OC5Mlmrr}p$CEiYm&q7)X;Z?#GndKq-Y~IF0Uv~KpB1~GZ--^!jN*P zBN!U2bGpnD!7M$-tfj~bpzQD{1mhFaP!(5^6D(d6fR&gUJxYDe}bdBTc-%xWnzpwo0G`6Ofca$%ldw={>~E&*&@jS)3;)4sh(a(L?B{-L{w_$$4M7i?o?{bhA)4M0 zD>o;bs;)x>qsfqh4AD}~_)s^J1{(;0>lA?-Y~dMnNA1S)9}*|)H0SNMCTX~CDKz8n z7LhPS2W&FYE5Gp>Jh3oogAcEP=SHz=dWScL%qYwQ$YN16N0T&5(=<;LHAiC>b)@x@ zF&S@!7h@!=4sFj8jR(vpd$tEga60xGmF9n1V~Cmt*uJP zC&Xk;FhfvSL9GzQ)BsESz|j>@%(2ih*SyaNG=bH)g;2gi6NW8?s`EM*q*T^G58S}C z04Uo0F{O&aQLtbR=5yM#fDQsOG!F16b_EH4CEXzY61Uv#i-y8kQ~_I>3lChVDKfGH zFaZ?`#VAbBT8Jwo4bBBe@?A+rlhGq1j419wCPyiWT>nU3l06c;Wg7O1!^i>4mBsc;^WujzqB4_kL z4SoOwj6uGHCcex701oUb$!r+B!5O5+JkA2MsHO~43YqEyo&Mnjz<~1rQ*6dzFwqj{ zrXh|nOA)9f4=IEeIKn3kAZ`jV5WqmKOn@1rW>9hJ18TtuT&M%w6hOM+7X%6}d?CWr zj=`ktp@t(T5EVT3&hWYn>td<{Y5_bxfKmR$Y|4s^J?g_T)b1YygH6H0EC{bMVJ@eYwYu;G9VAP}6S17@IZ5@#_}Q(C8$TC3GsuT{=`Ab++esk*hPu8I)E2S$96 zHu((E&>$DkfvBcxT=mREd_ebnq6Opt4y3FAR^V_5zzFJw1>|5pk25*xLb3?p57JW) zvPMmylK>JSLBO#Uu*KE1@87rtO$>of(yxQU6C59bf|77L35l`fF+39%O%@hIB}0eo zzzqyiADhioVu}FrfJ!t44%pxg48&5_Kn`&5AQ8|f1fd<=L6`s`Xb;pU5&evZu`7HcyY z0nHpB7^}7i4e$r65TRXlB=3yJIFZEDe9zJVgMfO^_vFAio6Xbe;CGqAN<6_5cKEJ1 z!TQRG0OaHn9>EbNq5Tp>I)TlIBY_;@;{(!jh#et4=>&v^I1&;h9kWFfI$?z-h!Y|~ zidg|;$x%2riBMts+Xn6f}?(nzm35 zp%hNR6<#4S$fariHBgf|l%~d(Y^lp^t!ux0A`sBwAmqUdfHH7Az!+qL9;yKdD$WOe zLIjRMCi3B5k-`Ha!5$8x9;|^meDoiT89$5x9}XfP*5M1r6tf&bm-QhZv;jO~PD(Xm zcuqi>cUcTL5)j5&9;CpU6JeWMCUT$06w{KW>?xe^#&D*vE7xvsLg1grAsQm;PA80S z8k28cck(JPIQADcq7Fk&;1@g!ZxR|!z)7Pk0}RL`otCVhY03{WdZMM_GN9&~hRMiq zHFY-w!#sc%lq?$PCKTg$iwpn|o?$btH;y`h7N8+Gya5cn6PJtZ1SI;V>n42>)tvIC z2T+R0eii<+dOFR%AaeQ{SN%YuDLOR>pv8VV3`BZ?&l;`MTCLYQtyfbXJONm6IkK;>IAPI*89{C*TS|oLVL8w zIJ;sSTR@w!hM2TRW7=+roaz%TRsas@xDD0->GF8uBn6LE3yP-u0SjOd%;AaJ;RzZr zksmN81|evLRs#9$6kb6ErVyD~fs*M+4+7vSG7>EkVZF;6P@+>3Dj7aJK)vY=74pEB zoBm0b$yP*Nk!7xwtq&Z*6I{U;oWUF1!5x28=L?G2%rTbL71g7DXz?7)IIOf{--O_ zPo33U-PK*%7@5p1s@4-AAT9;cD63*PY$l-QC|E z-aq}@!+q4pJ>KsfL(rWh)g9ma-QWKm-~(RZ7Z?G)9o*^Nu*dy`2cF^YLf>JK-y2@y zC!XRf-r_GF<1>DNA0Ch*UgJL=SPM+mk-sN8&=3`#wF+Sx( z)Z=F!=W|}?cb?~a-sc0o=4pB5f1c=z-sq1W>62dR9o<7`+2(J4>7!o$>ZhLStKRCb z-gtr@VX&U-yWZ=+9_+(j?EgXQt9|Ux9_`a!?bn{|;l1pkzU|{)?&qHF>)!4)z2^S` zWA2{s``+*W9`FPI!SjCa1>f)wAMq1k@fV--2*2A}sa|Mb_jjN7d*Aon z-S$Tx_XXzni{JQ)-zGAOG`T|M&i%|NGzn{~sV62pmYT zpuvL(6DnNDu%W|;5F<*QNU@^Dix@G=c#**Z1qmQSiVXR$WXX&uQ>t9avZc$HFk{M` zNwcQSn>cgo+{v@2&!0ep3LQ$csL`WHlPX=xw5ijlP@_tnO0}xht3Ee&{5Z0!Nt0N^ ziXBU~tl6_@)2dy|wyoQ@aO29IOSi7wyLj{J)%%a5$F3v4g568Fu;Igq6DwZKxUu8M zkRwZ;Ou4e<%adjN+X|SZ;FFj`iylq7wCU5RQ>$Lhy0z=quw&=l`0p>^&tGNh-p#wW z@87_K3m;Crxbfr2-)jBGRU~e~$)ii3PQAMI>)5kv-~P_MyLW)+46zyJRL1}NZw>eYAOa|9-+;DQV`=-`78MkwKgEg5*= zXD#8N;f5S`=;4PThA85QB$jC6i72M1;)*P`=;Dho#wg>AG}dV2jX3706h}kw_+~Ex48Mk(c#R90!_l~`t}<(6FLSd)bp=0@R|WR_{>nP{e| z=9+9C24gc18Mk?u~ zlscs7g_UNi>86}^>glJTo_Fbip_Xdusi>x^{_3i%#x?4Ft;Q#evd^eTS2 z_Uh}ezy>SquzdPuADn{`tL(DOHtX!O(9<vdKnk?X}outL?VjqO~i6-G(dfxa5{= z?zt3wiyOM^w(IV@@WxxKx){o9@4fiutM9&a(rX^S00%7azyud8>a^7UWAMTZH|+4k z5Rc}sm=RZO@x>TttT9#-SG)1YAcrjS$RuCXahxTmtn$h%x6JU#3cD=x%rw_*v$inb ztn#)DxgXsp0 zp7vG=$87uVy!U?B0Y1bo{P4sV&w~#K_#W2h$~W))^HBwF{PfgM?*sHxF^~QB+;^|j z0jO6m{`j!>ki7RxX^;N;?6+?b0_4XpKkEp%ZC8pau}yq6l$_p?(8w+X3ds!TFt!J%`|g9xxb|4$z?uQJ9Je+F=HP zbbuJ{pu$l;fQeKDp)vS_2_pXX(ZYh5fFEeMVOTz)4}EaKe*hFN3j1A#0ejA-lz7$z7K5qL3--v|Q|2-3x8fDs(h zaKb>&h(6dBkWM~IOZykO&6DtN&Rwk>=e%w+X?u#^_a zAr7Dnr6?J>6B2BK6R1R`B_5%IE9NAGBV0-W@Zbh5AjAW3XhRZAh(e$U!X3a2raR6c zNi4F^g)o%iN+?l^W;W9ltC+8CHOa2$F$N zyNCc7VzA`cq^HTAT2Co0n1dXK`bjttRSpTIh#uzfgo~Kc6PAdoB~Hl+RuG{kg|LJX zUXs#)IHoa2ffyhVv7Ko*^f-_cfWe{EdJ3h-qoqsHt5sx8t;Qk$wM5jfD$~!A+{AU zr7ZmFfDlxos)d!weZAU}{ldhAZb)H42v7$%NCB~9hR2*S0hgC@>iYSk8_D~SvoCg3SYnI76HHq$2AT1(D-|NC=iqcp{6b-@% zZ|nlK0?}Q*>nf0x*iA-t zRZ?XBmxLF*&-ku*x5iWRwoV7Z5QCeq*A(@#HzV$a?>aXzUxVDY&-&%WgjeEUnB>8% z25A8t#EM`AJA}ard2n4TB4LA&V6THt$c7!#;SU=Yo0!rGCA8rL6zk)}>-fS4L?O<= zCWQcuKnIDTK*V`0K_DZh2Nl@4*djXmmXWYW5$mCb30k%T#t0`p$Ut8QqT{jhz(N89 zVu)}$x&$y5$PbM;nMFck99ekcmk9y_S~w$&-S`519}&_M2yz_KfUavFDA9NyhPsLX zw?Rgr3~CfPf!z@Bl#}ZKc8jzn9d)vnm#dI6tHlGtz;Z$m=>bT9G2O0o;~=9UZGrwc zGP(I4tpghI4FS9Rj=bGB75n(dMfzbG^_I5_>5WI3BP5U;hi|#hJ&ts*yWQ`$w*#iw z$BNFppF)RdnqPZbZ>ZahMdz#oPA<`uCq(06{t!!@I#DM~2`$>pC|# zx}QD}gTGv&VJM^6!I*UKR-Ep7?7$l_J`m|fw2xn3^V1A4Jsq4Q9`B$6`4AD0cyPhZ zwPT8%3zC656x9yJto$eA0EZ8|SCnx8GeqP|Ux5%n6P}px2`tfxs_uclC!hpXBT)o{ z{9&r7wCa6RiC+g8f)ji2Jtt;y#SmzM6+&52vSw?zrVqNtYrQsq1%Yk7mV)er2CUX=-t+<7l!F0OMwT@Y zhX7eZbcFM8V3M&&B%lluClH&^f4qkYLPDX{Wc!f>WcHO{*oEV1oWKy9xilyj^WhjeEHwF=Bi07tJ z4gd&WScSVVN13>ZU+9Sjp@@v=h|5(!CS``)V1>k05CtTP)5wOiSc|s^Qv#ujR)~dK z_>0&GL1K`Jun2QQ=3T-7i-Ob*Paq7Uc!;AVT_T_h;z*8yS2b-mJcZDB@n8nUqXV^o z4v&{Qgn$lcAO&+)XX`}}EbvKoU zH3xawmy9+M@|O^UaDVO43(sJf-o$?*1qi*+4DMhJy#NNSC=rzwPnbYh0wDquxC*Yo z3Z;MwMG1k2PztBu3a)Tr^}`6RFoCb&3b8Oj9dHRB_?f0jfLB3+D`A42#cC{;6o-&v zP5=OY5QH%30J)}2F2)d+P()iWZ6S~gH^gg82mt=zq!07JNS6RvM?joGSO&@m3fP%c zo=`;ez=Y+A2HMpa@|16tWQyZ9bjrYo=QIw`Sa3ZjU>rqK`+1B?H)ea0UAA>+0&!$n zSY&5JpnW(HS(Jq?v7g?+T3QB1VPKW1MP+O80hEXhPOx?R6h>cwL7BLZ&wvf`#zf)P zWe)I!(U4>UQCck*40ad-WT|oh%0wnA5D=$t%GXi;*`Vv`0QqKh1hIz#*8$&lpB+V? z`#D-F+N0y}qt8%X5e10a_6#lxbr1CcGMZ&HiV#_pqn5~{%cu~3&~YAzp#?Fdy;Y?1 zd8A3&cETtS{5B5a5N^u%ZFm|{VSqtr1pW@!&|6`eR9jZ23u!eCDLlpy59|;RXfTn? z;COE4I&_v^A1M$VFb8k|T?U|D;YI*?HV_$*U!iIdG>JvSS3f4e39`UOr!k$t$GlJHmkvhoPtIWs8^JUPy`~_0mqkLxe8IN>Z+)Rn+{P{XEhLDwFX-UegdIr zYtUPu>IUIvtpmXVZ@>m^AVgsGtb&dDA-_*Ax|AZ ziFc+_wuPr-xMLk9azMI_SL6uVm>1bqTGZfIf}n3j#$;`Y5JkoTZ&$S|@md#$rUdbJ zWcOBl)JIE87Iz147NllPGnCdS-`8HJCI!C4dg`&F2Z~z1ZKnJ0R5TzPld=`i_sR=|$5GJq*dLV)%yS+nqd^K7S z$NGD_ij`k!xS9ZHe%7nFM!y&My$mY8@p=)LX%Lkbuoy-VmT<6}R<9EV!Xnsd13>~GSWF!( z7P_et6Kh=+yA(KhSpp%lF=(S45D#ist#zAI52cgBNd&XUiyIePBPYTKxx4(rx+RHuEDT#b5RT+zUMvt%YqX>LpMG^+iX2^t$jiO^G}9vq z@o)w@u&5HbI&hW_jrR^-FbNRZcxbQ$gh0+zz<7zLI_(?}#yif9cLq>kJRW&x)qA}I z(a#LzUUrba1ChOZCcgwhdZ+ra<{N>EAPKnF3O8hv39-JwCxun1t9k4PjbMEjUBCRb z&@0-|6aJB{`qTk)u)uEV1`LGK+n2|4@Y1vtQv|jK-fBze7p@hNR&2FaQ=oi|30E0B z5F2b)1?y+?`oa!SX&c7E{kp;jI94i5ZKaS01z`wPJ%SJmD-vsBy_teaVQSz>5IT&U zGN_zB{8vGUonEkYMQDV+X4dCf5JzaTG}}%wTLwoML`qnhkl|TpbV2eoQ$zO3iL8xX zhEGKoMuB%1T_(Aim`Dh!%2s<(u($_7>JlJeafPeLh$zZ!G03bqQeUUYPK$kt99}c! z1wAW420B(mXN!Ja5Put=SNdIwz1WTY%m+c(v)#ss&B;%i+eLRk*j#1~7u&rs+XHdh z{svLa*jx})=uf@y3r1%UyloJ^Z4kii$)$T{yiCoyd(F|D%~E4L-yD%Vz|HHdIwf#; z@c<9-5RsBF-|-C(XTZ+x{N9q9I_UhU$Q$47K+YRkJp1fIF9~{~BvJ$hN_LR84q(ug zMbh9aLJ&n&yoXAza6?q7N|7K5oFLIsS<&`8;vjv2tWcwea7vfJmCd)SyvkqUo8hUn z;TBO=3G4vQn#Tl;OK$MvKn~;#wBxv>1t^`s{hQ+yF_`fL3}+yE`sV;a4X+T!!AD&X znQ*X{kbzu&lN%PWT<%O8IMrnt=6t2qxRWqGF~d?=!(kmxN$db+?Xf+4Y7Q~}0F3}Z zMYOVYj>AQKL?gC@bbZzWk)A;S3c7}EOSqjl92uUap-nc}H1*p+R?T#3#vOHwR=DX9 zju%~4hfM(Lvz)iaT-gMN6M`TPv8#tr2wkAv76xEj&{(><3hg{i08|=MhSeDz`HB5JRH%Kbo(p}QhA`Mc~ph$N}3j#_v2B4rI z-Q5k6k}3)a5|`h+VY!xj@3rfR?|JJ!#^G_J{P52;u5per&N)9O4=#*dQm!sES0 zgDng>y5J+^)i(}b*&cr7aKUFC7@c|hjAKA5dJ={F=@+4tHFQmVHEz|ny86X9_-Yz+ z1Fk5~NY-^e!O3$4A_*g4-IFCdeq8eA+p`yXx6CpfA3lC*|KiE(aaYHC5P&7T1Z(D%P4t zqwhcyK(^MWLvv9JfO>Ok(X{5oyyi4XThOF;M5FtQ9Zz1xpS1daEPE*bJ$~N7$fWOi z4&w6ZGr`*7hj?0ZpLQBu@gcSmgqlenuj;x7*TT6-P9u(% z8#7h7Bts*LKf4LdB^56!Pu%o;-pZSy#lZlct$=6jkvb8=&6})ZOYM$h4u?jH=PYLk$<+3NnmJ zPE0jBD%oAqjl5aK#^K9erM+udYgx@=>Y)2vQ%Fy|+b~(k8XTkQ<(1wRzN3oZjkSut zYaT|)<~z?lvjb@-h+VyU9nZbiy)eqlMpwlRRi2r_TbH+~K(~ymPqy%DLG1PVbyT&| zx+GR))fcMjCGjQvR;Pz<9|qzs9N@5kiOO4D&I{kY(t^Kj@xA*R!s>5F;;uNPgCyy{ zFDJ&`@?epm_GI%-e6Pg7SDE+f$-^#%vIir@e4h`LWnUcjA~G<;9Y)zaJ*TRZd&+m5 zp%{H2xb{LeUxJJ!^GMssDvMi&C@%4Cun}9R`3q%+LdCaQ>V^18xkb`a?f7l7v;iKU zI;nI{mO6|YqGSpQ$~Bcc><8WLmFbS$crwXuIapC~C{ac9ur;kEAX$fFSy5dxc&sIW zKsS=a%mbD7-08X&zBK}mpM<}PPj(AKE{n&Eph}uSi|S*BB=z)5jr{h84=QQ8R$9!Y zM;>$osR-)mV<`k-JxH>KNlB{LOt<$n(#vPnZtW~H-Svd) zi49|kDrTfnkjZCCriw<*3ueMlj<8u-mre@8ddx}_@WWq`@hsaTrrH!2XoRh+jo*v$NQ-<;-?@4e=go`XGjQDhx(9p+Or*9*^U8Z@Fo`j`>{M@I_BJH50m|&+xPTv9# zi!NF_c$N8DV1IHVyc5#9l=;#ShKj|*r8dsJxH7rKw@WI#$4S)8n)gd*=_^Bp!A0uG zT0`ln=o#s)#INzV5FDRi>Bvao(L@`BoR}9{b<5xIcL}hSv!s})QxJST66X?XN!7F- z5|pvV@S~NnaeDn~c(bb683sD;uqO&N86$n5BdRb_X}A5}%XkSX##Y@?1d-+zEo6fr z!mBo+NF|}jZg!6mNjkHmTR!2EkA)D{w?>uDGvV`>t8ffY5+!l5SXSf3($DffN?d^Y ztulSuS(@_*UTy|CMJyp*aqd)Id!fyZ)0n-2o1Ou>AcBT29wFfBxu+sj9d@JnlDps?q6=t2xu{ zoMsw2s&OdSyx?P5XVoH0+~PG|GLcuaasHQN2&t zF{LzMXy_%T7o=lAm8Y6;bxU?QP- zF_#2yA11x!)VZx!Zlbq>-!u|aSv66rnaWP6whut?nf~9h-2dm~+xNa#}qYE1JuqK3><9z%^XDju+Q(LH9J*JKEiT z-orJ(-HEP0?L(M2E!T7Z5f6nJgQXK&XbiZA>+vfrug9D1%#cV0iDGMag?SNTlA0w; zpFxkr;9Wn?)o=j=BCe?hbeef78_cE_Y$Zk`%0WIGQbAk3Pj-q=4l7tD>)I&ZxrU1s zXgwY)U@`b~w!dyhn5n~ma(VgIm*JEY_0ebG@~v@{;abyvaQPN;k>SWt#N-E}XFeS+ zba5DJcZ`I$7fRlXKc-FyRxAkTVEZg?9{YJXpF&EQT9tWYl!9he>^oYaDnU^E=S^9f z+ufKNdNQIJUd{NMbea;=U({2nl)OXisjn+A-=dBCrlkf^C!*QCoa_{B>9?pUbE3uz z_Vm6+Vct#o#xn2SOKn7z6FxYMoVj;i#<~IKqAk}x_)Xr!L_w-CI0l^w6sC#-xrH|2 zw=+AaVT2|W25?K>%ck#wYBL?HwV5&5Vn`yrT~_MvC>u`svA9QgFCNk;wpotCOG(|O zS~w04-ZtGs=)9-ImgsA-^=>k2wLT`tt=u~|5}U;%XPp2QvESG&I7wADIZUNeMGi0a zmESwH+q7y!^{5`X)pI+ai#~cs9m?azd&efOpBRpmyEGmxd=5apzl1F5lR=i#f}s%k zhAi(|bTbFzK8CC;Ql5_#L0!TZZbfmT0AGHoTsl!Hs6BwvF;L0o*q`ILC-URcr4KiOf(KoPd1x|`&k)(iLTnj}_3uSu?)p!f_S_{oV z3oTOXRL*lz3vq;)$o4t3fR<|m9B8u9s6N6W%uo&~<2b1g8WYViVyKS{NBbj`doSpa z+PI0@9JE@9+v9oGq7d<^pTNNz*M$j%wh8gm@)xv;*0hPWw~3FpU0-XHIB1g`&qOSF z-p3f}%~OCR25<911{@_BWcb2Aqm^-U~rmVR0H^IH|REjkOkDe%X&NWaWlY zt~A3T*aazV3Sf`KAa1c`e{DBN=`bwlFskV=ZtpM|mp7$qRXgaog`{xntksmM)53&N z6RYE4mF%c*$9KL?+t5zClurACPKTP#JMEq8XphX-I-S=#gL^t%i4<|9J2e|*@#s+Q zNtOD&K$)TJ@(%6tN$K(}=<=)Sx)+;mP?Rg>|b$h%ghe|yCfHjxDH(#!|K)1Khs@Fjr?#Y32 z>OoMTV{e(swY*){^6}ovwcck3z0Z+U4|(Be_kw8ByGyVXs+m+rG5cQ2F;*Z^)u!~l zDd>A!(^uc#*D$V18LCP$-`9lH-%Qls!qnf&-`}Pi)mR|VZq?uE(ccx?-<{ImgQeIZ zF5BDQ-#^|zu+~3#&_85#tq+T8m^QuG8vc(9hgoTm?;>TE2*Pwg`@1RwCSQoIVMKA)<>_t8|6G416He7R@)#3&H73?ECT=|_Ix!}3I3|faKE5_4#XK$} zFfJ?5FD*YVZ#}NyIbNwdu9!NmTsW@6)uZ%kTy0`peO=eDeOv>1LX&txtE1xv^MsE4 zgsz?*5C4Ro=fush34_CW{nQDgR};n^6P9Z?3{^W#4kvCQPnw5~njQ}QAeyw4pS04O zw9lQe_MEg0n>2Ttv@4u+c%`qKJ$Ywh(rJBCabVIJdCHaerug~fU4bcg`6+I$DG%!@ zFVCCIrc>UjQ@({$K28NPPX`N3f1{cX(VGslHrP>{ z4iB4-Of|q`p0of3r;h2^^TCLT>G;FxhuLcJ$TNw|Gf4}o@R9JOo0%5O8p)nBkHTgK zQUX&t0-=MU^p2UV3B&ewxW|QXW&&N=#Iw1~MsGvm$%f&u;AV5UW{W&$i}h5{$p+KH zX3Jj9mJ2A8Db1u>8&(|7K1Vi=XosVaGAcAR3c;GI(VKe}rUF}fpaPW3; zofGrmL{tAeaPSGZaHQ!$BLl{%0&_i;@qut~^xu)-QQ>~TaWcR+z>&hy!xh89!NJ4B zBOo9kA|fIoAt56pqoAOmqN1XsqhnxTVPWB5W8>lC65!zx;o}n%5RecOk`WS;6A@7m z6H^isQ<0ERlaSDmk^;~IkdeVaP7VVF1uZ2d9TgQFH8njA4Feq=10y3d3kxe78z(2{ zwQJXSxw-jyd4>4-MFa%I1O=}P3rmQINQsI{i;BvKiOGtK%ZZCCT)z&WC?TQr6Uq`2 ze?U@F`A;DwrF;o#X%zq&85LPsRXI6Td3iMj1vN!QbtNSYU>U2bYO1Si-MFExrKPK* zqo=2L)4;&c*x1;_#KhFp)XdE6)~#FS=H|C=-?p%@u(Y(ava+(awzjdcv9-0ev$M0e zw|8)GxO3-@qobpflasTvvx|$1tE=nXyLa8(+}z#WJv=--Jw3g=yu7`=eSCa;eSQ7> z{O;YmcmMuiYWn=7$ek8ynl3n*cjoTL8P4*xvrQv-4^91-gByW zQG7$jk?ZEpZ4JjmXD1?ixhxk=jBD1OuC*eERZrs85P4gvJN}0f#K*>)*PD8UqcLc?njs_N8x{xq_~vUM?i+D1EB91KY4#!f<4 z0xid&=nd)03MGBLapR1ssETn7$8S^LpQczC7TSeJEaoV`33vVUu9vkmKM|;^I-`;nUy|(BTu%;}bIA6Ea?aiGc6|L`(#~fRKohkeHDO zfP|5lgpq`lk(7*)jGU2zf{~Jnk&2p$nudv%j+vgGg^`hknfc0>L~%fE2j6 zxUOEk3aEjbo12G+myb_?pI=BoKtxawvFMk1-@FMPe*;59Ln9-j|6@qt$&)7+Jdl%{3#b6b1qB5Kg@uJh zMMcHM#U&*rrKP21Wo6~% z;K99xr@;Qe+v$|cKm1JAH#oKT?)r2--oO(lrPQ3+S5~sE19ooC21Q=`+Y5cQ z&)+u%B4X00{Z0GTBvl7bBZ|k`{zDeRMPJTY?j07J=c-0L3OL>82)EE%VA75>BOj?&#!b5F(%pNM(J(ghl`V}j0VRD zw;H5U1)KO##4|s$j-$+hvtJWrZf3?pqoUZ#6X57Cj7A?5R<_`kojydvo;I+jXYpJU z=q9Z6-5Mf#y{XqlZll47c*})uWu55Zp8No%iIt@ly~`Og8dgVL-2kPFE@C>XqS{Lp zz8R@DRa!a%n;0_btmY8Hhh%=5QWwOB0#iSl@P}}La6WLwXlR(|=s1{|cvx5jI5>p3 zxWssPB>4Db`1s@m1QdjXl!Sy-L_`48#KZtJBqX#XBy<3zqyY2)WMlvgR8#;g)YMmKXjo}!S?TE5=;_%R7&sUiIhmNaSXi#Hvhr|n@N;nqa&wFD@QCs9 ziu3bd7Z8vX6qFJYk`@-05fPCU6_pbclLsWQw6*LqSZzw8hDl2QMsA{RH>uB82)zZ?}*14&xYoM=h zc+;q*F=HYyq7| z?GDVFpA8x%&ry&~Ztj`1ielr+?~ZY=Hu}-ZI&+9TpYKhsw=gUBtzNHxlK%<&uWSE( zPt@yQf1Imx|C`!>GRLjKhBx~g!@t)4LF@nM^HSSz4DF4^hVPfP{|9_ww`SS!f*xiE zH+?@mUnst{(D>l&eCvodVD$SHxgqthf1DNs9AAhiioAV=z7tu14}X*WH%*(!7s_Ps`pt7$Qex&y>(Z z-W?BDiEV`Trmm?1{xYRI*@gk z_*mG4I5@<(c%*oM-~hV;Y9pp0CZ;7O0iYuxrT+yCB&2_Ul$3##?C%0FAQ?FTi~-3h z7%3=$)?=cgW~QcLrln({r@z9$$jZdT#=^qR%F4;Xaqa3=ULIZnegR=2AyHuwaS>4o zQ87s|aVc?e>Fd{JE;XHmq=KXrfTGl;xRX|r29Qyb0gzRal~tCLQ@#*-Dhi6Kib|?V z%4#Yq>OkVDtKYbB1E%l(pSb(i#2t*{F8J+|+!}$xYiw$6YHoq4yq30>*7jD|sMpa3 zoAx@}+q*hCx`62Ggy}vo^Y!-h0FE1&o}OJo7Mi5=6+?j%kO~W zz!-P;{A!ZyIgG&Bofwytv2yFG$_4X>3j${PHlQ*wL6(rM}xDp zs!%nR8M!e_S;tZ>L%@C8%7<`c@Tugz6C@RnqTyWm_QL%>SG+OWU;q#^X zAY4YM+^4dpYn0-*)uwd5JB$5qsyBvm{$#c3KgMx=fa4(d2#aAcH0jR`Ax(!L&)?ZL z&A+>Ue7OGpDCJgD6S*O)?b84@q{#U|BpUae0BHFpRWLNMy5x#?LOK-MqPIyaF0(6> z6%I)=$}th~+T{T{nx>4z63t(6<~%8W7;{kR!w_=Pjd+7k1}=h56dQwnE#W#ONGFOo zpGS-ToXnOssk1rfDId4+7H-N9;g_+JC&#OD$+U0z;O=!a3>Qic=qhL7F^+G_ai93V z$>O`#Ei}Nz!(EI7=nWs4umASTU;-1mU+Ikp;U&C0oFiN@Fs~31foX+?hK7NTj*W?l zi;a!{tCk}mASWcGAR?j!Lhh%EyA*Q3vAPs;FfDf>#LB|L#tIu_VKXckV0pms$_vI!J6>({SKNJvOZf`j9wq@|^0WMpJzW##1L^uPlH@wzaacx3;}wW9MY+;9_^@uDzq1y_1K7lc$5T z*Buw{J1#zVTzwq@?)o|2z2|uMzLOga&H(QBT>w1pyL$NF^@PFA%irDmfrrlnFW&%f zzX0ERf%p7_?mq|$2n-Gi4h;$YPa0Ui`&U50{nyN{KP4PcaDOY|n%Y|$ds-U?+Ukef z-;Q;>ndp2o+4W|or+%)laeknAeyC%9q-SnyU~Xb$c4~5Vc7A?-@%{UyrKRQNC+crVEsDQ0%8N^)TL$vvhDc$ z$zS3W{CIS7dT?_3<>d6!$?4X~>4%fk^^?=Jp8(&%-(1q$#=tML>x*T{AVg}|<|ci6 zBr%_ZhY$WLpf_4CEM;xWF5fPdvJ{`M(^;w9#ILjVd5hG8`8M|I?fI@qJ``6zAtKXV z*UXcbrO8&j%GO=DA1$)rQ^nq1N?z&Ep`x{;|BULLJ~E~Lu2L1&Kwf2u*T?om%Z0Q! zvYXCE1;RIG2gFpYY&6we$=A`JU{at z+wOC^wnxoRA8|b*URs98&O8X7Bvb>wmWV%;GmgI2ockJ)m3r9JIp4CR>aO500iIv6 z27D9pn2g-8c}Y4RZD=hLH*ydwxgZywF3weMHLp;<@<@1jzGV1(q$~Y@K}S}=L8pV$ z{>4uRjvCBJhmjH%CMFIR79KV>0S+!9F5cygNs3QEMnDLoCPE@0#9-`1L<|UuikO5N z0A{R{(g47`b>OOBSnA{ybQF|ylvMOo)b!Le479Y2baadi3`~qnFe{CXjU5X&A^vhocT6-_lYt-tisfu{xxHP{UKe}JF<@6!>?Ndq1lFwia) z78oC42HLx(#>NW=t);ECwY}{^SpBA|U^Y6;Ne2L)+Art>{@U+=8EqHn1#WvU%x>%N zzx3RP1_y_ShDL^mM@L7;#>OTlCMKt*z}yMUHn4ub@c#Y#m6eqbA3kgYa~;sr=g(h( zNIN(U1#6z)>=~vrB^3mdXrG7^oUvSDpkhU^{&U{w_lHc$7hgD7WbYl zIOZ$v%#(}a#daN+lu{G#P_3AuA(svtZPguyV*0nDG_!`6)@9HDrz4Mm1#c4RJg<-ndvRGqyKFAO` zRjHXbX67CrF>*_;Qku6YcY_GH(&9(0WA^ZM~oBr zBZaRI!%aG9fZf{UZk$PzS_`@W8Zy!!Ha#|i2!c19)GyrfH(4D91_m}37A_9X?^zuQ z0Rbt<>Mn!2iCAf;1N)6~$oOzFTx2XpHGpH2VwZ2C*7bD?uCsRr0o%}q@$O-3aA;^4e1L6rNhRas<1jXv1blKqC^Iv&v$KFzfF1(G z0s=k2EtjeYFwE-e>e|{G2={=m4?cixK7E2w%^{fMU@q17-yTVFUXD;H)6q-3)>>&=4@Nq^it(3LBabI$ zxbtUU@s7Op$9!ZxQ19M1NxV%^vG`6+>!c7<*m)v{9-Y+8_t_Vbpm@e>@fcoP?|Xuf zIXbDlui8Fymi(JTD%u>BNW_Z6>1*!vjql;seoAk+w*fiv*14{9pr0s6jMg=;_lt=J zNQj?wY>ijCv_CPuC;#ck+GFXXPk3jN-m5*=__((fCq(wiiwJbg@%rU zj)9Agfro*KhlvSdMglBs077gW03sY*B4EM86cRo$J^={s3ZJ9wrVxCQd36USDzOSFu?Z=$0|+Z|i6~tYRpJp-<`Y*IxUMWD zp)4$+EGnrYE~O$Nts*I-DkZBb4fGI@K?>@M7j1&eLcw3L6-XBUuVpI`Cth?3U;^mE zLcdH9VO0V^Nf&%{Nk%YF>Gu-Bh5EUaK7CzX7wGQq{sRNu-2>e{e}bN#!Qaq3*wZ`M z3(z+N&<|@Oz=Fr|iRr1S*_oO7*}28}`K86hWgv%^z!Y~`LjVcnCK%p!w!!cQlSW^@ z0)Gm)PRGZ9t^O5;`t^7Il&vm~)H&N2dVlGycfVw8jV8Gs4cMxnCzOQa$;FX6Vf1>y zcdEAON+7yB3y##;kk01y2S@7Y*lOe{B|ql(1HMx+S%zR3I8sNZRHxEmBAR$@^10s2 z4@8)+cQvZa2FebVy>@471?S#|sn!H6;WW7#_Q!o@wm+?lR&$MlFW z+;EzIYh6vLwkNc=^i*IZ&2f+a>&6c%JV~7EwFg_O2f9<|euquo3{{nmkNoHA#c(4# z^Pz>e)bZ2Xl+}+6wX*k*V>gZzylExP&nrshGyLKD`CD|a0FtQ4WsnLOcdWJq6bsBhlP1xAHSF6h6s@beoW_E zatK0&(qfn%HvMQ4(5iZz^yI5{Dhe>#W>%Gvq1=iy8>5?15RSR00tZ!GU!SDs+UJF&2~HnX-dytdo7_NjYquWRi~_xhLqjjv-n`wO2B zmcM-auz$3Bc>ML~H{1NTX%2!wGve$QfFCb$Nn@7;hKWXX{Q139F!EoV3HD{VC5Dv4 z;X-ajBS^vs%&I+s1)K?nUIhm`6D%(loC(&QA?UuH66p@k1Vg@uK<%KzI+A}aj=|JH zn{BL^Hd}^zd+5FTBb{9RSKC?+`cL)Cy*f@j*>1k1dY3lPb4Y6x=P)(txRds&z0S>Y z*8kokN3$HS9sO~prE4RSZfgfKsn6+dMTTPIni545tjO(B-Pz_KcWv=~yp}%78F3q4 zc&Qm(Sa{id;|==8_l%Hd=9?oJH;T=uYXXt4gt@&dQWiQ8nR6RNrg>1~X*!*^x- z?6_%or0~WMLu$W;^*K+S*V&KkHAFufw?ssof{ROvhj&>(fVB^PI@Yk>!DZtB7RCJ;$Ux5u%GpsY_dYr{%dpn>(jc=;+VJ~H!RSaw2ac49(Q5MNNQVSV3!O zbQo6G1g_E4%*@>T_sd{@+u7OK+uOUC+m0@cp}!s7erJx$FPEeO6w?U=eTSf92s(nG zLkRi`L7yRL7lJk*Xc>a$AZQeVE@_5``=?`carB^UC|MHL{fnaq(Rt32Bf(Vn5JkW_ z3$3a^Ih;We=%gfbl~gXv2lvr+iUy+j?<~&0;V#yAB87Q!MxL@U>_HV1tlL$?)>5P% z0Wm((wO1@Lc&TzO^sH>6aGOpLY2$Eb;st6g!I}PKg?y0(wcnkcZ}jsuUiBlDeS6MI z5!=0St7)O1jcWoh!}-z+UaoeAbDR*D3By$ctC`4u6P{siafN@QQ3Ooz04nkGzAI`)KUa~mXUwkAY%Q~x{ zS}GGRYQBScPf&RoY3|f}pI0aUTIqxHu5X{jK5dc)te>4+Szf(WfB-3%45#OpI6XZ-JA=;8{{p`;3Ef{Z36>0sDC`hZ`A{-Q zz5)0{ioxUpDrZVnR;?(I_SX{YD(k)kB%!xiGx#?BiTv&oa;!SVH^Kxlznv))6}0Bc z`=fAmd52KRIA-^VTg-igg$^(XSMsjy}bhWS9zh-<_82Gnpgl;xNVQs5(q5 z;WHQ7UAZ$U!9P)aMbgDU4gHGE^i7K^j?_<9y&552rtW|=oQ_*M;b4*6mjxpiY{Wrbxa$Z^aBMKv0ipY4f%2cQ&Oeqg{~7%PSO>I>7%y5zSFUid zv2g*);pF6o6^&qF7|7sZyz?&y@R#|!in6k*ii(=5s=At*1^{Rl{lD_~f1o`6Z?ewc z?NI)~Jb&g&&j3pr_|Y&k8pb$FOE7O|d1VdQJ20$$SY7+Dw!X0r)=j{!$xol>x8;&A z`ydG0-#Z(_rGQxhITruoYn|DT#Mr=?!1l&LN?Kc z?nHBiIBKy6NaUj{O5h+0hKh9UW#wQ_V-;O8og&4s?+(594Tql$rif#Xo|5z0wRp;j zP;f=Y7fJ@mUrQ=KCMp|^IcpFIJvnfYdvLUAO=$57+#IWS{!X*w=j9qGuVd{@vmP{>fs^t*>a~3l)VX3MB)s*v9RNv<<9Y3I}DWuKxRb(kYti??5 z+-=5)6dgl3E0zfQ9uPZ(D_uot@v5^kNxOc!oM>0xD;4xQQ)oM+BgNIBlhi^lGlNm$ zk#B>COZ-Ho?IV94r{0hDwd_LI{6)?@w`MD4ANrqs{+?h`-z)iU@!q|TLxFly+@{@o znI9@2CHSNKnrRur@gNYvOqTy~_MoQ+)kKafSFWu4Y~|2;DQJ<|fg`R}pT&t&T|--1P4m$BC6xT664VMPr9C8ZmnX$_#FqNS<|psl8+ ztqxk(8akSqy4u?My1HNm1vUbK?UVm2^YY(TPe5c2Hcozz%(L25has_NBk2Qg7bH)BStwaEXhuYIrIud4?njaMY4J6exzsNR+*Tj*J~ugC zOM6ZAlIQvJU>yrKLE$%9n8BazT3A)SnA&!>O?cK4tZOvx z(KaeSv;_`cKXRTi7jc)UBl5m8S7rKRT;$B><3?=+rege_$l-R~bHayC+;o3L+=g)E z2qJKNe?Q{B^ix61eQBv;V`BqX6&Dv57_0dB_`q8wBqRj(DlsuJa9Bx6Ny*5_fX_++ zA|uM5p%D$u|6iW#uVuc!+vbCHl)?U)uD%}Fw76+(U}$D&Y;I&?VPa}&VrFf6%f`&y z?$&MlTels|E$+Zz>3G}H2?i@?i=VKxcCxW^uy?R=bhL1FHFkH`^YXmmLKRnDEVT%VGsk$F$k z|1+Ct|5!|bB@D250WxYZZh&WUe}Dhz=m@;SFIPT)TAaT-7Qm|j7R9#*d01@g^uq+-`$}TAL#TxbRGzuhd@vm z1cm>C2nhP;IENz6pomi_;yV=a4T`uF6JR0ZBH~6UG^|fkpL^{zTXnly5IYx?mC-fo zi@2V4J*2ztP_NIEuN*t_EX<^bAhRZ!8(p-zGp=wTb?QlnI6Bwdz*G8H@^evljTCdf z4~Ai;T*0LZh|N0LN9#e-3LSe*KVW;+VXVj7xeM z8=du)`;Yen{nQLR8Xe_|v%jUkt?qJwH7lSPY>Q) zb#8heBbo=H`^I6=%p*k!uEC->zQOd+xLBHrIs-xB50N9jvjfxlF~+9JjPdecjuRhB zX)!V-la|({Cf;E1B^1(y#@3||2g0q>B=uMd%xo!`j1xJf-)IY%ksMe{i$Q`cO3n|) zZ#}kSNXgC?e3fh~F}~?JoP`+XyRLp(H*+g1;%n)nRG|g(Q4t9YfrJ7(H;RF<7-&sB zW5;RWY?`x5tFg}-o;Zv= zcVmC6T~2!&F^;c`iZK@NolU!=^w@aMX!_#j_fWjGw3y1=S!ZKBf$6{#{Itb2d>lbn z9QwYht+IP~0$3s`H)!yz^X0g&V)0{P9^1^>Cq&wWwn%nz;_VQ;{7~2aF2Z@%x=rjh z9?$cZ9J&H-G}~R~YN{?KIg}o#8i^axG>fNF?P`y`83J}luvEvA_6un&@7XJZXw>xm z>{jCRSY^D~8E$6Htn?~1^F+fjS<5j}*U8_|#mVfh znT4CCjfb4Qr--8$x045xvj^o}Pa-d0Y;RvQA75l&KV-jqDEIH92KZwH1z?8;U_}IA z#{}TU1>immz)cFkOAWwF3&c+iB1{e@P7WbU3ZqC2r%s5Vi;rSTNaTu45)6AJ8Iq|O zn4{%iVEmx?c3_!9NQG;3>HV~l*u3J*@}k0;+?P#Rja^TA`wEALswc;r=B7Iq7W!8f zrnWz>e%jmma`5@!;On>jZ{PQiPxepFzMh?bJ%_o*`xo{xFp+_w3~XgU-v8_aeg*vE z@i}yO27Nt+zMMjzPNCgXX!{h}I)gUOpbuxz`Z=_E4l|;sA!rbSIw7bDf@&e?1q78r zP(B1@K~O3L#X(TyKcww4}u_n2=aj-HwbbD`0>N#7rLiw5&V7s4h{R~wL4rg zAY!x3jJ?>u8_DlU##aW{?tG=|eMD*8n}tpQ>}`JcZGNBX;?`^_&l4(&nv(upi8u`t zFYwxMe&6W1TJ+I~FQ>DLf-j>ijaNsBkezuND^ti5T)QK`!)45$VYK?#BUMjtIt84O zhFSaC<(5;wZYQ;)w%#)0m;k&w-gCQSf?zcJUPg+*`Au;DF zRyHX{h!?JVvyJ8|r^`TzDBhVCpC&w38A7bpd?@wX;}{>7&o5INg*?hY*zUf;vNLU8 zj3c>7ZLvh3lkyGQfgrm!gWZrhq?fYfVA9NDkNA=19qJbEes-IhmMjlZ>X3y1d(%hR zqU<(R@uJ7zyl}?_bNz&n%$E@cGAp zwIgr6kaDwUlB-Z-Ww_tTBu2){g{bW7I*^dV^Nw$o?;Fm;IZ6VpF*vg6lZA5knUC<)-yc*?zOTi2Hp}z$v44SD zqUu%KEHlhOjIyCuRdpXSYujr#wly`3mxS}<3DomnemZea8~H4~H;iu8r%U;Mhz>Wcw@3oINNn`Vsp4%Q3mU zC6qilUf%d**LLmR`-Rhvfuj8zh5-uw30P2yVuou2^NsVikssY%*AX;67zwGEJCmd* zT2{pKKk79{IbDgfQc?~s%QaU>84>BV+jCFX?h{Jm!NEI2_b&(VZ%F zGb3on-5I}4PdXzbkMlJ#HnzZ*U`t8ah&TA|n*tV|H=XJ-*M6D7%D_uG3{Lca{G}`? z%PxJBP5my5Q7p67Q%0v3^q$SsfkPCLbEI*r#EF|^54RXXSFd`{i#SY*+f9kvPK(>l zh}+JI+s=#IE{NMM{>0M77x0(hUnj(E$HZ+%|6`D}9hI^jm9`m?vKf*5(^tRk=ye$W z`gQ#G|7KXi7GOxyc2LT$U&gLi&b~_ltk~RX(Qsea+ zWp8t3e@Ep&=gWbfw}bulgToDjV~vB;O@j+9gKKSr>up2p?Eu3Y-J@GQ<2!wm8-sIe zBa18JtFzM^W3xMb3wzz~_uG~ZJ6FDSuO0P%I3Ci{q%k9)6w?lqpy2MC*QuG zoq_=8FJ9x|r^~fna15{%Z?~5R zW-wWqHKnRGnZ%2w_^QI5RBlTn4`uiK{!C$>$@wVm;-Pf$5Rx-x{=(sWn<`?{6a3O) zN6l2;Y>E;4;S#M<2W$Hxg%?(@R4~xPW2dw#3~E(~A~Y66y*|yqG%m)Wep6&)r`bo6 zR*+yh(=Q>PsmCSjwl)3TxWDepiq}^EK!1JhwXUY1A%d$F#95+UIkM%HidxrFR~%cyF5an=4SJ83?+TB3oP)dV%tCvfqn&q2IT()?-hG!<+L)wop{j%RE8C*cU>k zRq$himXt@$3kV_5t9XYrpV`9qlU%nxr<>N5rF9-0c>T*<{Iyn&{ktU1GtogGuqIj%)C!9jVq32M__xiroLEm#1y+L-jy1Mm{Fw09-{eZAPs^SS$WiezNYtZ zI>XmSZL8vqji_h~`Qj6=m5doFv(Bjvsq&9unK{}WNE&Y8t*k@9n5ui^3irvkEhzpm z-}~$;544E_s5~!xOZ}X$fFVq%VS`2>fT2G!lm20Yt3-G!Q%^O?%1ZN^Y}G2B5?;uT zeAQ?NJ7z6GLi%?GSld(_cUlV!>bV;cpX&)QS0o8zJuhW%#~*r2AWAHmfzwHjiJ?(%8{fuIeM4ey$$QBryS<$ZBQLh&7Aig#NLuA!cN3f`L0S zyip@JL^;Q*bu&>3`_xbO50XDF=^jpPl*+kH8p%3UO_>K!6L!h*y%q@jyqNNOmRou5 z^_t>ias zy=p#UoS~fR;#08|#|{y_byv}b{p1FVX-W5TJ)HgB<;Hoj+KS=cZ}U2F62h$!ED!8H zMiQof-{iBhImhzNAKdPH_c>kkeK!hU+DHuX(&s>0deliOMv3Z5S!9mL93W(=ct zs%&Q2{Bdc_go9E&_*NA_K=X zG8@KAgpdATVsY&|Fl3gaqmX`C+(Cw?1dBWT|Ko4epNl*H4NX$8qWoJ#3R2RG73Ke; zVg@*2&mI_?jt+O6?l`$PI=ebL-*s|vb9QxiadWxre#hO@*2Bxv%iGMy*U;Bb=l*^5 z2M-hi0%QXMBm)D*gMvhYgN1^F`9eajg@ka0gm8q0vH?Uyut!C4#>Dc)KNLwyl}^i& z%g9y9DpY$?dLys$X8sGa!YZqx8vEi`PQ|ZXOI~}Fy!I}A?N|EBzqBUkd0oQmcTXDI zo^^EB_YHK8j=~Of0YT%|);1_F0FUvXZpi)FJMF*Y@4$N!yg|VPd~|xYd-Q$z;M>@j zugyEV73=HCtIMIw3!Y1}j>}UPE8|9Mqq-l4Z)^;xYz!!W7*zc*tiC>~wLYo;Vb)}G z*=lFY`Rjq-@k#LMdDz(>_GajX&-u^o&CAjGmoazg8Ms}RhVzfxWxwRM#*ke!Ng3s1 z>G=F$Rrt09R{6)E3UAT-2*bFSXtPtBNg;;r<<8U)g^&>AZ47zgv9$u(=`%Ke(s(VV zLg+E?D8JL{aCv>SUrJ|OqF{+?W|_{cN0RoPQ3;E8WqPaC4r=6a$dvyddv6^U_1f-z zD~KWj(v8v$(k0z3C5=i-OP6$aw{)k{BA_(V-6bW00!m8F@LmHhmuubYUi*Ic{p|hh z=Xu{b&SMT{n3?~6=kL1C>-+tDzhCe0mFoJb75ax* zk7>97n(pDH*1!$UhmFZL+)^1Nk_mjtdNuip9c0^$cyua~kYZM%@EUeeu2{tt$meP1 zM;&^M-`k9%94%+zideJ;Xzc1gHm@lA^za@ndBh_U2u3s#@UA68plJr%TeG%lV|(?e zPhE-*w?w1QV<%o&*_Cg!vwSCbSL2Y!fAiX1r&vwjDQ`roAPqq{RJ_Ngqhke&epn(8 z<9%=AXej;n0^{ zCNI2;JWv3Ns2nAlpphGmOPWj(ELS|15JiDhDIcoD$%7wHgyho|Pm!KPsiZwZ6D0UJ z;CLxXbHgNG+0=?=EvdGDYB|~3M>;FeTq1am{h0{%C%-ZFb!uB3wV;AbSF))=HhAKY zxd=+8o(9Au>Cp0Y$+&4Wl5qmFi=c4B>a;#PrJo;IWP2li z5iF!jA-P|&n@k^2E)Z+tlQ~kOkP68ipDZcwsn@HtFWNJzGUKT;FL+NT!Kk}IR2lrR zhOwGa6#ax(PcQ?Kg-Mc@)7eUNgzl*9(MZ&Ca08W*_?QT^mgk=EpvGvK;P=D0XEa&% zWMWN*60pSCywJ=%yLn!JG$N-nYiYwCde#mEd4uxTibe2UoDeKtF{-X)Le}nefXwR9iR$1Oe(M7+WMZ!2lMZ*kvKrJM@RU;%~ z`0b^ToC#~x=;H$2A!-`LYavwpC5+z%C9Pwm@fEFEU*DWdq45r0K>hXndjV{=%C89i zSKS(jfvlO+< zdMWgzT2QZEz@VMqxF1Y9|5^PD6VOq9<8glD34Y^Ae&cC=_z?(&Jvb%X8sXI8f_tnQ#T znLW4ed2KOy!=72aH`%>5**!1*OPr1$xE$uW?Pqyy zC;4qggscZet$QV{J5?QD8F|Dz1Uh?1JPwLc3QJUtO4E(WF^(%RODMHTEVG8y%V(FB z^zvEKD=SdR7nM?O`J%!yt6J?Hz`lZAR}~jk{W& zb~T%HH&}MRw(hR7?=E-lDs_Kf;NO`U-VvMD9#s79`I|R3?X_lowI+jgPlxMGM&Fu` zw^&U!*-SM$PQP)TdE-0R5V_cpwo+fZ-uQO2sqbs!%whZ5Y3Jv&o^8nBH^|IBWc3KT zegfS*gMI}wPY84$0y%&{4xH0_Q^e}W#aKo37a z4`!iYq(O{h<)QUkbbg=Vktv zw}v8MGI;fe;PJbtFwGt~@G26s(69mOUlLKDKA_o?h`w<@8|IP{i)A*bRo$q}mrr1^ zOGuU8$sf$PqCYEz9y&EnVr+()nodnpxUBw2KB5aDV%w7#@4}MhQ0dhaQO&w zZ-Ds|Gr9&Wb4)5Zmf7hcS4L4Piz)N@4EOWxIeNCmvDPf;7J*b??dx}{CQl8*4kv4E zy$AA6!ZT~#emBSoLapdD zzyBngIZ|niICR4(M}~~GM=Iw&rJj?JQrJvVL(UR)STvN?@EIWHyhZrV_*ykI`g2K8h zdo~4+to1*pW;D~4++TOrM5~=%t1lBu3uwT{wm5^cs%yA{E)}=I%d01hp;pvQl~K+1 z>i(+EbMtq~2x~XuN*x(;gfRzuQMtZ93S&`>BHpZcf5V^Q3lF8Iv#qK(1bwB4`1R&) z?uRqquYHmT`K$w_pBwEWn9q^-A9xeX+7Cr~J zk)Q4EZr7`qf3ZYudn#zyaKBDg_xrZSLHc*@<{KY|zO}!4XUEgk^wQ0^|9%r1DG$z9 zR_^gV4}NxU->y`E9 zJ(rdHHy~kDudiY#D!;!^a`y5yiX*e39f{o{JWD(3xpb80O3nhpgFsRgcJIFm z1T}xzP#}$E)eHEh)M#($>%Xj$&Z%{yji}o?V5e9`GSWJ!ZNb6U?8^SqfJxUavIHZy z`NgH_xOG;CAb13Mdwq1hf^fuvFM(dO0UYnV#Tm68xO^;MZ1?CwwKHrZnC_Cer}(6k znl6$M=}fvqy91n63obELYu}7`5f;w#7(jLA<>r#Qd zbHa!Yx$wl)iyer_tjOqmm4YtxK@`**?|i_PuuDS-v)3ftlYKYton{{0yri`66CwOc z2E=!5lLle& z?Y*!VML>ch?f3t6b7**#1di4JWNUx%^Re*>?>sOuNtbr^LWV5}~9 z4nOEs_&?B9Fr>o;)q#wNh>Vz+?9Ls4i=hBi2Qo5B3JRFl5I7BgQXT$wa|q~FKsyV_ zRs4K>g8ckK0s_KRS2_b)Km_@=$ydCa0e!X7Y;9@*TTfAG6a4qw55n`wWfe z9JTj6rOyJD_X4%&0-f6&qst7l<20-N6uZp?=d*Ec%dz`rBYY;q{3b)9PX~pb_KBOd zOPRivH?LK<%zt8?XlxTLrya;d$fIS6t^+1Srp+W_g7>V6#&QsBcgRu;gt2Qd<`$oo`U+&KY#?7<%v>`uzZUv50`D|E%AQ z#HPSzB-k0~vLKgLXLacHPb4Hf+jj)6IY=gT#Nq(l-uvOl41D!s0)GBaCq?795l(vZ zFvxo&a=sxFgYe$IR2=`U&5gB!CIx3E{}dUHxc(d|%6g@qssfc17Nd}kl!qh|FXhq> zd-jY1CfcK*q7tgGkHfN8!J%eQvG?LRH?uX!c#jtUHyE2}QuXVHC=l17p5R!rcc^Em@%mN2HH`jY=O zBa^$r9oF26zMtjU!-9C-zdsgl$vlb-!NQ=p&CH}PxhS^FToratlqhJKltEUU>Xradsm}wCW`xuxQTw@#% zC|i2z)P*WS^Q3Q*(}|a34I?sQ{JI%(yowGd{)f%bwQGRrcLNa-G(Z5)`a$%&Y=ReD z8(09vrKRbj4+2ZmPnO?LC^L-9{1fVTVSoIc<@XchchL|j$;ha{Zi$i-_^ttm9ssle z*aq}F{`>arv4_jpjTE6GC06qgg2|oYFBPiIh)5V!nzpzQKsA-pk zS-XsRvw~%V%Cj0xo0od_`DV_kwr=r`9?{Re!o7V%0s{Wmgi-t_TH&j|;?V!S`tOg9 zYnYFq^A9v;z{=_y?(QG$8W`^yoai2$>>ZjK7@i&+nI0OQ9v+(>8J`}Tn4b6p;N}8u z1Dw_$rxrKHmNrM1w#S#Z=T^Ud{PgwH#7Lmp(_Q8ZtYDIYiF(w>rU&6Eo-Te&~~-oNmU94@fKUVF$KMdUC+*J#+(nLAHYV?OLrFXmP9S!- zo$nlc%J8N^I2ycv&DL+xm6i4=@;=I=eG)%L>7xg{*lPAmy7+2_3W2wE>(Ya9O`Xwr zC^-Gdf~bp*1_c(X>(D}|J2i#{zSUKspyDkh}kFHk-~P1sGNwEe$#GYnY!s{SED;em^}1Exu{Hxjmawx%Lp?gx5r9b+c8EPZ>|(-IU4q% zr!v^(xp^hf5jvsTNjPPsRl&$jN{DPz?y2=FP(RcoKt#}dG^UvhbKpl+@;1k%` zcz{0vqv_xgg2KlqA|N0lBqaVp)%o|36dW+pDWIJ4UMv&^`1yqe1VjY|!TL}_SQso0 zFPY6UVq!Al;f)>Ouztx|HB=gadCf_*7L74$dVF}(gCPCz%BlJbF8SSx=8B*fju?V)gZIy z_ZA6wI}Lx})&Gv906J-~ru@eS`J2h}k^uIvA}Ris00u*V0o&&WI4AkVEwO!R|NQpT z019(}0`tV>g7sIz^Uu}duWQ8?U&I^6LJ1c~^K3^?5P`!p5FaXEx<2@!Bk~%XS?+py zj%3tb@d&r|3i6&%I(1IIja{R5A41IZGxbs9wj?;aJ9FK!g%5m%d}A0)sy_|;NXMgc ztnU{!7oH#9BsNifGG3r@zBJo|xul+FT}{Itx71yN1Xgs; zsBY_5;kqof$l;YlNZz})Z^|vFDh~?8vy13d5C~3Z$Gd$g68u!yf;eiPj;qvdaAhjM z-7!)6MRYrKl?;v+eD@e)n1Zm0|O5ELQ-o{6~!7 zADYOe+3HW*+{>16NvHT5;}xulEM57{V{sk+TBZu3@D=ZSSTUyiSAoak27I;2zL;LD z1UkH1u6ISQw9nlK#X86LD0>rebz4(46{-4Pa6TL8E85FzP3Q82OQW?f91KK&9J7T~ ztPADICX?CISr;i6;YVYHxnk1`7oTSbV1L18n0{$gBOou6P^9T=GGAQg%Dg@F+F|yv z{nq45g?H`;#i~+u<$5g-AnEL3aV*R2kgM;Q!+#ic^Pjuw!SxP+p_hCJ zV5kJ^0|1`6#1sEq7W~Sd`ME6k5C8Z9CN94MIo8gVOl$Hy&%EhJ`H2wNw8>@Kl`{3k z{csphVyPs@MR<@PEIL#bhTUP{xWmw-GOsrX_i?40^r)d+BAfYVH|HvFA@V*?IlCE` zKNKStTF*zPlsS@#V8ZKe#{RjtNHtN*A(*42zl^cLHNJ~Id#VDhSA;JVhg>7pVz$^F z4|hw)&*A+7BysY~!aL3LI~KBsmeS3t2$)QBs+lYABQXn7kzK5fy7A|Ykr98th_T?v z;7G4f{lohI;sS-`bl$oJ%jrZ#z0B$S!T$Y+oX$%EFf6JQ?;@uY42>`GzkehEUyh4G z%)#Zjn3A0QN5%m-PNoBhaYkkk-pLB`TVQ9&F#Io=lw1JDL4M000ONn}mcB4a116Nb zxcJ5R1U5TSmXcPHmQj_FeF#ccPEA%$T~1zIPC-K+)B{b02bvEQwG@@Kl$5oVRkT%9 zbyObes6NzvsHUr?{zzTpk*3yTE$t^dI(m;D=|6d5pnnls_4NOQw9Y@>x&huLKOaQ& z0TLbThFw(ps_11^VR>yqWqp3t+q|0QoZ9y6x~|OnzO=@n7w^WCnug66TZebLp_z9;0X?u9yZk@94Z}LDlJ?pH9V>Z_*4@3R08-^?D*7F_|$jtsR{6?aq+0J z@u)HJs4?)U(f(XCXn1t!cueScTp*2cf9K}7QK4!JTZr2+-D3?04LJtjdm z7Cufk9v%)(UQRAvE^g2%?(_2U{cZY*f|RhjtiWRh-lvLO)@m$H+Ke9hbU`L`G0*5< z*f8bTGM7DLud-oz?ZVsSEz#t!(j4%lImEOj%&IxUt|`*#ZMbWFh}-KRx9R}*azFP1 zFVCdsUV#q2?soq6wgFZ)fo3*AhSp(Eo<(X|#;TYn$eARG8z=J_ykLEjO0AoIS1W^1 zD+5WC5gs1fGqVXoLgp}2mTgdX*z z_a-T=rY~B|Uo=~#Hd&{=v(9X>$!)jE@3blEvMKGeel=)WIbm8eYy5iAsD8!h?V54( zx=H&Ni|#F(p&ggmL$9Tipw*Lv_0x>cN11E8Im_$$^YgDJ``?VeYZ^QXNAzSm(a6v=vfsM3art!(33joQ9bmq0ebKbde8zr z>;M80$k71gcm#4h0Xdn4oGwDnRv>4eAdt@x$QA_h4FUmXJ^+FPij+T$K7l+1IIzJf z(kI}OMdEMzN9M(=R+au(0LwiJAqCaZ2V*TDTHrejtSBzNK;BN^Xx{<4_r zkNKVAwR{Sjad!-h9hLkG?&v!zq(N01);b*z`N^-zyXJFS^f?zj>es4aHC^K6jZ@C@ zp%}K1<@)$3JTzJOEAk;HCg1Dr74lwb!ijp9J(Cq?V>QKWwkB(>h}D+v{@&=>I~7p# zj1adiYqluAQzon0#?#Q#N_;zoCI=SN)5iKZoXpn47sVt`W+l&#B^RFa?{s$H>nG{W z3$<0?;`m^Rum`an7)=qdeN^37_C9aHTJ*Z(?fk@_gVv;zrb!x4Pn+i5q(63riAj!G zcROda&{|TqC<30fgth~ml9KR^BrJ{Dm_^D}6dbOxT5sa7VRfs~N#EE|X(bu17~V`q z%w3ItjdJ|N+X30I9~0@yaVy1*jwF3Qtt6$J!uL?e!f{Yilsd_`(NsTCO?n9j;0N;2 zL~-ZrCl?CYl2J#{K8&+n3GZA-9OJImJ#InAM(d7@mWt@k6&b%`ZG>Q}O1l^jm5Qi0 zLm-$lHnTJea5eTc-Hnxbp&lTmn9Ejlq`;xajkx3rUo`RrKge&G#2S8_770I4%sikH z&T_A#cKFT=ennn#EOH*lBQpb5Vz_uD_$*uf2oAY1D!L-gT>~;v2{Sb5`)3w*Qa0qA zJfDdsin7g!I=1MelHs%PtugC&4I*eF+oiW3-pk)5oLq1PO~ncqs@&0)u@FhkoUnC1VBGgCV3Kzw29;$}=1p z=yRdVNyI#Y4%x)_g~?Syd!`ZQ(}OoWqIb9!eI9G|8J&mKNiItp1UszUaKNm88+(P} zjo0-YI12>A>NjuN&YTx&UR@sxxZ0YtBcMEUHYb3{ch78(b7Nx#_5N$P@jE(cvuhcG zlxvgN*J<;l)W2EnBL{`iV01Mk?s89}*~MjFku4U@JYwNNLZU%Uy)w02_v!5`6hFd; zSJcYbCz`@DpK&KUD{(G7MVr5As91dqzUFfrquA89fK-|#?e><`t4P~~&<@O~^1iG( z1lw1n;~FBCP-cFN?jAGvX_8Uir|cfg@IFsglxVMA>+o40@^i^f#vAyS{p@JX!9122 za-`RElvQT`SnL+38(yQy895<@_UdzutHKCYU#%JNhdsjzIC3m~^*u|kEN2Jt%OX=? z*L0JSb8(e&%TR~G@%ZE8EFml-8d|cQF1`+dZ}YBZ!8Vk_zscAgKQ^@U^u1Ki3Nv?v zQX!p7Fjl-sZIp26L%m>mLyc&&*g!{Ta-@`Ab#MFQmq^x~-Zz6N1YeR~|6-kEqFYKq z&+jL_U~h|@zADK`UE`{FWU`vMH)aNEfHboVXh}rW+i;!m3W;d3r&^i>LdK1tK?KPXeKmSoYaUa z91#S$@(dAoxyNZA9JhqiICmpNMQLysqfu(TSu!ipF;%WV?zWVsA=Kr@%I0lB6)sW< z*qrXf8B6I|4632awB8uh@kYz$!Stvz9n>Yo(71r9kHgqNy*^xDe=2-QC3t(Y;VRl_oq zY-1lw9Xw;E7CF3eXCc$9?`|!$p*l74M|avhj9rlB@O>M2^Pch2ET&~&;=8#T^ra)& zMYu#sqH~0A;f;EW*Y9hv^x!SJXsUcT!4oyPJy0GnPWE_@v!Hc=b}>LwZn?})v~}RB zjd{J&GEPGh?eI725cs|n9a*$-hwBg3lY;LJh^UV0*a;J_lJM&1 z_Q~bqUwtOFg4Zu>%aEiP^0Yi@v~YW$B(Ho z%-k$2Jgls|Y;5;AIQY1@1h~0{czH$m_{8}6B?JV1Kf3>!Z*>`q{2QeB&skTO_^w}= z#d4PrFKKB7fGq|I5C2P3u57HH**~*(vbJ%tv30cpXmop5dk0qsM;9k27#sb$i;KJK zbI<2)UeDcp+}wTLJ$yYp{XD$0(^Y~{(0zLOhkA>RCsc9cxp^|dQ5mm zY(z#}WJY{cdO}QEV%&?QgyiJpj29{SS((N8`K3k0C9s`lO>z0_va0$wO`Y8XwS7!fLzYAM&UOd1bznnuYzWc?{a+fYuiUl0h59E6T}-qtsX!OUtR6F`E(afHafaUqp_h z_#;iOa6C?&X#9Xmp;8`_PPO!yY^i3R55g^$w!&zGiUbbZLj$c`qw|Um=L1Fsh1+ar zv#&+QI_e#j)LaYoz6#ua!T^U#REee6b}C>I+=aJp;A2OrhI+r;x}^_=%MNG#*iJhj zTj`N8V#<@xu_d>P%^1HqbWI_s4A-JyIBicfa-A>*A=z7px7|u-_0ZAV^~q=DdT^Y& zSgmYVK<=k;vQ({FB$bX|#Lutqq?#07_*n4HuFW325+x!>)J^xmen!^>L9-4a73k9U zJ8owX(GJZ~Jm<$Ijk>wQbT_LxdT!o?ipqGwgoJXKiO~ z<6vX!Xlv_a2g`79u?Gd(mBVuf$LEfqoZOs1IlDVMySun}xVU;;G%rtgcQ232j8Jc1 zUl0=N|8K^Grn~@r&T?{c!0_zfp_GDS^Tx(Tz+MNy!HWooA2EIHKO(c< zUuN|EWD8zKApgV*UJ?cYR(${{H7^n!KoZ|2Wbnr{3{Xk|ru0W>77SGS^R#OP7A1O_ zCVCktdYLH-l4*V?4PKs{U!Hz}$@njDv&#4V7pTi{kCoP(}+qOFBhG+J=| z0aMxxR`J7J<$@E;^_9NySCUoA5cNTL>T(m85Tld94}2u7qdk^I!X>(|X>U}f^*R|` zFLpYA+dJ#{>_Z#&y(_i!*18L20qC;EG~Sj^yjPX2!VhH@RtIn0-5f7g|G*WgozddG za--VZ7g-__QV_Y&XhB!oY3|U4nmXBh-m!4shcmKEe}ved z@K{SkP7DpR3AtU&eV)>A(c3*4psscnmwnuk||tl|0{ zkzES24Zmk4E{C~~n`DG>59H;z@*O6r*^4b4QNA)91<4t8}@UYNQ*|J!xd@lW&yb_Nz2<3=Rn{`$zx!!)e`bFMd6>y9Q*F2nd%) zc0WUB{wIMEf9+rY6pZ+vKeX33FfcSWG&V7MYWCC=Bpq9sm|KISV>3%TGb?*@D+lvu zjuy|HEUcYD(y^tDtEJ6zOWWsGpzNMMyC`dWHyZ~xTL(8gM|XQCcL!&8M;8zG=N`W9 zp21$8kv?8=eqKodUTJ~enL*y!!QQzcKKUU&g`qx0p+3c7zMx9N{Xi8*_!mb66h{UY zMFkZ`1s6nz=Ea2N#zthvM`b0%W+cX^B`5vY^{>B-IsUIZw4YsEoL*j;`nWdnX?=YC z^XTWzkuTfBn_q{wcSd&h$My~<4vr_kpG+U0&745yP9Te?F#OgRU{nKiFJMu-7zlzH z;2&m!e{5p^tbgHSBuOcebcCRTK_4vp&lguT0I=P2yJPQCU--m&edr#8K_8obUkbO~ z=fr+T9)Pay}@pRVS!0-RbE636GkjEPrFZyXV14=E-+ z4SI{WoN>w~YpgBx8IL9`=NeFEk>o;OzY>1?{L2aPoMU3Dw&M**EZEz5g
fE_FBC&z3|py1z=@B;ET{ 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
|-^wGX7fumONfjX~3}?!(phZ@IkYV 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
qDYfV-c1jnXuZyyp|W=hFcrN=X4u!A>eU0g4i+ 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
uNwF~*wzj<4@8!w}L& zLpj3mU4Sxa0;DvPWKA6iz|J1~{S^I*@=ER8_DArfnh$rJ?c> zVqA!#K$TDmoFYtMR)%#|2m+M@nAsfK3&}i$_Wj^7B4aWt<1#X1GY-!V>Yy`9<20&C z5E@pv=nfpg#N%}F+c}M3%P)RQh|UGFqOu|<2=?Qd08YoW#mSJBTR@RJVMByfm}bb zV?UulW!1wAWI#eFfCZ!gM^$2aZ5k$K7<7?=2qXjpjDW}-9J!RE7^0)K)I$qcfFnA< z2N+-VV=c%x! z@f2rr{&HJv4&iJnrz|ms*qoSdsu(UlKuJDlc53H#a%XpX=XZi-M=z->reqtkn zGH8Q3=z~INgi7dyQmD2mD1TOHhHB`Ba%hKo=!b%6h|0-@mZylC=!v3eimK>}vS^FC zCW$7Ni^}MX(rAs^=#AoNjtUlx&Yh0>=#K(vkP7LL5^0f!PonUc5Rzw%8flX{>61cf zluGH87ATKW>6Kz>mTKvia%q<`qm_DTn2PC`l4+Tm>6z9Qm6^l7oXY9` zoYHBX+UcF*X`brop7Lp*`stqnYM=`0pb~1K8tS1UYN9IYqB3fuI_jfBYNSf)q*7|7 zTI!`@YNl%HrgCbhdg`ZwYN(3psFG@_n(C>dYO1R0s8=Xvuo7#r8tbtJXt2H&lD^$FVrVu79I{I5v{GxeTI;n+qp}_; zq9m!amMETRE4GU3xRPtRn(MhPTezCZuiA;aqU*cDYrM+qywa;IxvTlOU14SBx7JBL za8$hlY`_Zaz!Gf1!pSg1YqRC+w>2k|w(GwdY{W|J#8Pa%KZHoz#OOfUL>N?99?^&Dv~;zHH3qAhYVLY_{y23`3;d z?9dWz(Hia12525YERL|NocL?UA#K!3?bK3j)t;suU@W6(thfGboII`7f^FD}?bwnn z4Pvd%g3i{mY>zrE+#KUDumA}hT8wSM3XDJqgun;@UfJ?(-}>#}g6G+;tJ?Z3+j_0r z`k)SwK;F*ni{-%GioghrfC%JmCIRl{Vs7SY?q&+E*77XFE~(+d$=Bi_8N9&aQtk*e zu8X~Z2q<1hYyk>{00~sV=GyM<;%@HZVCOy$;Wn)3)<~i}?Qs%s@&4-I3*hYtyny5? zjqy6~wc!8{w1^Q*?hHKd^GVwkYyR#N01S`epj-m)<4+U*VdKw`M?4bO0j7_jP^$rKZT z4BRaVd;t=*z}@EU3jFZ2q<|NXzzPVhKrS&1lfVq5%?*eE{s}Z-!i2zhSuqyR?iM%1 z4y!Q>2rU_Oaq@z3jU?~_LvSG*@*yMgT}kkbH0|M578#@f3`=h6t`Lfdga%7)-bOMB zpNR?oG2=Ew3t+Mavw(d70qRcj36o1COY-DaZd;ImDkp{;Q!#18@+a4_9Y?M!Kh{=` zG7Oh8idbgz6f+Ma^B2f& zE6?r}DRV1RZZoTh62!6)CvrQx^E<=yEG@Fa_Al20ubX^rJ*dDO4-OgZGh!-6>xuvi zaMLN{Ehn3a36lT{gk?v}K;+6mD7Nkh;U(z~BV2q!ICR z#qvTobU#vV3WV`=2;W0@v_wa4-YCW}m&p{9KngHHQRIQ_vh+uXKnk#jGVh&zFhL98 z@#6+{7}o;~+$|?#a~%JJ2-C6T=JZY{7)N_FMy!Bb^z>2#wNR@_3(PJ~#`9UDby};n z<;-*NQZUDM?dZZy4!ki|cSIG$@(iE|87wY(kk?bEN$Sn6U57@$gRb1mzL0nhOnw=N2t@efFD6VvkQ;zcA| zGuB0JSVpyCP&GG~c4E+g2sn0%oOWujc8dNybZ@V9aU1t>BR8D1_4~Z_|GF&Ph%iET zL?rulV&H&fht&uN_Tox+ZTngblR*LlG`|G(DIu>G+p;VdMQfY1c02KGr-*l-5qLw0 zO^^27F7P1F%m=fD0T*?BPcBWvvH}lrHj{T%6L?}oGH73m>=JThi@-x&i$E`Sa$ER? zV>pH{cj>5X&;G6j?@Ku2?TC-KbZ^`ooAp$?cPGcPgtHlm!}vJ=gAJta3J~@`$S!XF z1A9~UdRw*$yEyWWcTpg3jE}g1hqi0mh=20{5HGHgiGy%H<54i z#H@HiuX8}R@}5gB3gGb&yl@P^po0tZ0r&V2*K(x4_;^7G^5(grBRYVKc8=KhWgGgQ zb5K3lF))`4my`FFzp+%`c1L_Vr4M+h>$!v*Gzx^Vo8x+}>v{vjIf^v0T!(lF=(vgq z`6)&BcB}V^qX616!3w0V&EzY>-*pKdfL|W+JZQE1htO9z#C(Ef~k86 z4|-xeGzq*Rj+C>^?6X*B{`+Vswqn3^5ifjEz;qHrNW(w;jeI(eeEL`Sd5sJ=TlheL zcT|f@`s)63l4o`ZoIJwQe2oBsk( z2_!M~ih%HS1ogK2$dmO5U?&o?y(Oo>-u?XG6Mo^BY|x`mp>!+2-^tfT_UzhYg1vwP z@Q!WWtzTgYe#qTK|;*JDgp@}43R=bh!qPPK8)yN1c@#SNtWzF zLd1=d3-hEHSkU4KBmYiTv`7L8$toyb8bnB9h)IeCMTk&QkjTQ5DOdih#{wZtgfCdS zw24q=Q-%hUmTaL>E6IiEdI>{{Uqk_>ck1O>wytE`1g98oJ6 zwb~+u_DFbPn1z_g@g)*wh)ygZ2||Gft$Hw#pcJ;yFR-mZY>JEqqe3AEB5~A_$A#P) z2>#44r)-YQGR-^_%{0|qlg&2Wd=t(%<(!kwI_k(6wV9<&T6gGu5EFMH)3)yC!eHPkirJa`AYOTE%+ibPn zmfLRs`4!U4Y#kKWLlIT9*G7lQW3JP_%Bm@ln3c8#ro6ou-+cAmm*0N<{TJYX1s<4S zYT1R3)^V>ZSJx`gW%RXmFYJs36)5|Mj1>@~ui%b7{utztMIM>tl1)At<&;%+{*8=0 zfR#>RLbc0P*N0=PM-evK?M%O`2$E~%poJcq=%S528tJ5!UYhBY^I$`faK(+eI)-cJ z6=K)U%YzI>JY!-71k*|($)?Rd8|}2!UYqT<-F_Qxv=wO?;e@H4&gyfqmJMe%!tgro zzy%+i@WKs09Pz{zU;H>l!hl-psqemZW^=*Y`{s51ew6XfJ^vi^&_y4e^wLeIR@TQ| zjvTYe@vgjKoB7r}j4vQR9rxUI-<|i~eg7SJ-uwPS^}0WEopM8C$9(pu$*8@HF1DDS z`s%H}9{cRI-=6#Kz5gEk@WmgW{PN8|AN};zU!VQ<-G3kc_~oCU{`&3yzaRhn_1~ZW z{{8hGoBHRX_O%uG?)iP zaIk}u`{1rZD8do4%MN+e;~x3gM?d}%kbxBBAPHGWLmm>5iB#kw8QDljJ`$3Vl;k8S zSxHM?5|f$Kks)aO3=*-wA|6QBVV=s*ct zP=g*6p$S#!LbC}>(;#n-4r3l}fLT$CUKFDl)#ye!+EI^w6r>>)=}1XhQj?w(r72bE zN?F=cm%bFHF_q~|XQISVRHGghsYzApQkmLRr#=;` zQI+acsajR5jx;q8C8pkt89c9k6|7+u>sZNJRX1dPWR=2(tu5p#?T|z<)SjRpVvXM2JVVBoc$zB$-nbquO zIr~uLeHOI-=xk&uyVk6Y7PYBW?P^(T*U-Kew5=U%Y4v(q+1?hnxz+7%5eHk}#+J8q zr7Z{(8(iZa7rDt*ZpePyT;wgct;0pEZJ}FT>s}YT*;SQux%*u0dR4j-wJf!&bE)zM z$2#9F>~zq}UYMP?Rq%}sbt*&O-q`my_*L(I+0qbKrpcoUuk2a_;yIl+ z%DKsM>xiu7Er({qM`p3V!aQObKl#b5vGSTtOeZ0$*~oW|vzN!LnJ;td&Z{AEmubUi z+xS_>fgVne37zKR0NKfNw)2}~T<5GRnluLn^q;NF;WketjOrmfThP%aPNAud>MCP<)2QyYgMqzlXhWIXTmG`Pef{af zinq9^f$y#1H10;98{OdkHoNmJUwco>+B2Sam-YMaOkY~j{5F%p<0x!>>-pFU*Av4> zW9}qBT-g|x_Qow-ZI4Tw()iAI#a&EsE{j|=AV2xGQGT0YTb$(Lp7gpkzHoO}Hr}Hy zxXNpcW=$U)$tFgzv8@f_HZPgYK}RvEi9Wej`+MgBZ+e)APAaL3naO-kFwe7%aC~!J zIT>EM)Jfdtp;z6^Vz2bn*$eeCOFHT4J^RtI-uAVVM8svE`-88}@?76t;AA&C-=j|J zO79)%dw09x>78c1|GMgV2R#1ADc|RhfBf2xRy=*_E=(LRzVIanFy{-;_N)gS?fnkC znA1-BbmN`h1UI|g`|kO!^P2H>etd;H-`|^?w&Uq;xo|X%=)}vN@MBND(tF?f-IsUp zhX<}_QUCYZU%&RCiahVNF8pRgd%|ExPLrRHWyu#^=&3io*EbJ++`pa1Z+0E*v5$Pf zpWprU|GD4IKl$~)fBEtc{?bqXtnc{Hul#f^`~L6r1TgoiF8~*?@+R>6F0a~_BlP;u z{^$+poXz?W5CTV!_H@tnn2-C^PjX1G0qZXR=x+drZ`%%V;6$tWB6Ec#w>10B%h zi0}6rY~OTn$K+1|Q~uBd8;}P359?UZ1tpLHk#GQ)u=qkt-b#=OXYd8D@B>Yy{cw=e zN)O`TF8*rn$GUF|o9@WyPYqpA4F@hP&d~3)a0Nfm3&HOU8IKLoP!IR837PQrT5k#E zunvpx57Th!{E+4HF!%gW0~N6miOvzv4GRqs67|pu5i#u|5%<>665+59`H%ubF$gV? z+A#3_SkDb9G1WZq5|vO83(*jxE*1??6I0InI&lsGP!-?n4dIX$ZP5;sum~S<1~U;9 zeNT?ID%66{{|*fc2N4r{QS^M#zyc2%8By)%@DTCN8n+P*%WM&KZUp798!HhNWwBJQ zuM+)C0bwwX{yL26YEVtc4;^5LH+32_hA z?Go(~6BQB{v(VKV(j3bX<~Xq%$ME%FaTXI!*9cN04N??GvI0qw&`dE3^D)jqt{^M2 zCHHY79}ys3@)q9_8^^H?+tDGz?F3hk5>?K|DAFHQa3B#;{8sF4B8tjNk^={A>j*K+ zS~3r}F$bse7y*$D;V}TSG9PjM;< z&?>{y2!m1<7Y`CmP#QC`E&mZMgHa?ovL4qE46jlpg>f)_5+u)T1<%njak3vD?;sHp z|1>ZDGW)VIWex!wQyLeOBtx?_Ow!{}(kLksBW;rZYV!Olvm=X=1yhm_?M)`_5;EBi zG4B#Je-S8GlJ-{5HgPdE-}1hGa2Z3a8K;dbuQ4h^()o_?1a%Mv6LL2_OfL)4E7>wJ zkMSe9GC9r53cHgXr!(WaGBw@I44-ocdonh8umiJ?ET2#qaq}V}lPEjWJ2mq>F|sf_ z(lYT9|L}1p^V26MQ7bEQ9iz`R?Gpq65I!SwKGicV33NaaR5UMi6-l$lOjAB_Q$Sbo z4}DC@>XR1fuo&A@;<}STlaD_2%{c}17s1Ro6?6GI)Et=(8Iw{Orz_;jvN$3171jRo z1|`%w6>|R)aWkDSFp2Ui!%;h9lRGDLMVnI2M$i?<(?_4~Gj}ph;*v;r?56bZvrI@j_&$5O}uOhzXaGi9?kMfC}nlr9Ce zOO2C9EsqRcwNU$1NRc!eZ}J**Gw}$ON$oR9EG){+gSDSPT`O!`5G*2aRIsOr}ApsLn zvCkSU)doG2TbtEe-}O}&l>!^J&>j`gP83)Z5hnH2>tGZD4K_Qg5b_jL20hftxC%>fVM>8)fxbU-Vax)mH6B7xnTGuTv}K@<{`gUAxuwT;*6vRraz} zVI{FyA#7$5vP=PU=GxLK?G!cRweHf?W#!USjf_t(bzJ?kUMICzcXA?c)nkJbPtEiW z3%1jsH7DJXPYaW0gSKU9R$niaUr#dZkabpB@=p=-Xa^Ne0TynF^XjNpXdSlU+>|#l z&S%*cY&o$9k0WEp>*+YvDSa+eKQ=%El?S795QX&y2{2eU&=~LL{%2!uRE;!7gEL>- zO&2vaCN~%8`Vb6@)fw{=NUip8ClPh`Q)Kb>F>kbWx71Jt5jLk*UX|5rRrfHZQgS7B zA$9V1jrM9sH6m*_TI~|~ypsfTb#{w)GOcuV?{sXZ_i@XXyErs>VOKUmbZBqVd4KnL zu`)<$5^aI?bmJCyLsfROmrHH$6TR^<(f55x_r{Wu!H#nj|8~{Hv~eM=NFfwD8P`fV zuu{KtHrvth#4lwxHFFV|e$hmApEp&BEeAu8aL?pZ0eC$HxIO>xGar{iG>hKYjBK* z6H`MNkEQQ&@zYjQ7+g6M@~TxZ`ORjD4Ay#TIZ`72ZVcRSNl) zr`dBJn3}H{Wn%eyv3YF*H|L@m({NNdzmuEG*;D?k*__k4W3<`1)cI_@8JbT;oIm)2 zpG_X!8K2EEjq{nG1BRV_tDlz!o?+#d1KOYiY@ZKWp}pmwVQZm{W}v~jj~|+%i3Xi3 z8l$77p{FmSRVJc48l*#7q(>TKH`?h)`eZ&DrB|AzTiT`J(xfT(rJ2i_mvW|W8mDtw zr>!`q>v^YB%zi=bjDMP_i`uAnx~K2?sPRRmm)fbH8mcimsV_RJ<+-LcwyLvQtG61P zdz!2JrK!JKtjC(H%i64wC9Ka{t=F2Z+uE()B(2|CuIHMr>)Ni>IA)X*tM8hx``WMn z8n9KlR`gnM1KY3<8?h5xtK(X+8{4rT8~(CKnz8xB3N~N?NFbP2V+a}`0XQJLLLdPa zU|1rXv`gExLvylSg9siV0TN&Yd;1r*={tl+e>Tf4XWu~NG=RA9F=0Jw3(3Nk&_;34^nj z`yZNHzIXcr#=9x(fCCc11JawjFC4=&e5|~CHS%Bs62Jl$U;(J2OqyE-MgzXX;<#5} z2!J33SUUkaU}uH{v>QObKO?po{=f!IX2NNEGz>hs6PzTP8w7@62xx!=FkrVk;J+;+ z2N+<)HC)Q4oXWq7!&T!37(f9~fVDxuOiUcbgRa1pb@>9(*BifCFS3%}<;phTsDj;JH7b&7I-}GQiUxU{dmY3q;@n6d(Z z&*dArzvabS+W|(P$^T&sK0vk?zyokV%(ddq7h=MZ8@v-h1yX?2*`iord;v1x1&GAJ zS%9^PJw;l;0$Te5aDXKKnq38eU;|{m0epQSbbG!>!qPEa)3ss;pq#`>;@w+&0Xm?z zIo;D8AOyA`)DwIQJV3S|fIhxJ*h5MP6V7NIe14 z9m1P}-+3L>7s3WqJ+wQZEXdv5(OumaqF6$_wJo3pqC>fH;M+R@)i=JiJzm)xT)|6z z*ipVD>cQJvo7IzjCGxzv`Ftf>K(;LayZ<5NKbP_xLIy^Ew{6@hMxgVJz6TcKwlhG<{hkGSf44e_boqUcQ;W9&BH}A0YStA@pTm^V{Y0dAqp(;kGdV#yy+)H9tM{-nl({`fuO* z7or|SK)!pM%!3@anS0NvBm+WQ2CzTw6Tt1C03a+RK=H4j!Gj1BDqP60p~Hs|BTAe| zv7*I`7&B_z$g!ixk03*e97(dI$&)Bks$9vkrOTHvW6GRKv!>0PICJXU$+M@=pFnr| z8%ngOQU0Pkgdlk$rHa$1Ql20YvZLjT1qoFABk~}@hCBvABv622*uNhrz*rIZ0LQ;! zEwui#!5{&H6kU*v!1{p5zj`4OB(Tte3=$F~{tao6fP=vfANfkSHF+S$aXW>E@eo#wq8Vbk=F- zop|P{=bn7_>F1w-1}f;Fgci!sQHUCq)KX17)rKkaP_IfzX#6 z?068RNghOkV59ZG*abj-%9vxljVhf5PlTD3K{Q|mf~aItTF?lB^;M9tuv*8eK`mhAfp=Dhx1^Q+o;m~u z1__98kXQ30M1345lTLRQ!+NFXRw$Pie33wnXxVQeJh$077Nf1Mz zX8Z79)P8H%4Yvt)Zb9irT#y|e2gX2EUoPOVK^JF?)yDrY>Eu8BPHPaj4BQZ~l05s` zr9z+*DQ2=+b-d82O&Tio)Kph(_0?Est@YMickT7pV23UC*kC7Wwm*w9<>;eQQN_}1 zmX(Y!y9sEZqd^QW5JIa3Js}XV2klT-de&)nP|^k|k%9*num+y3nxUYqn*9 z7;RKwme;YSZf*(Ex(-r2$GD7S2|=74#Ns&yVC8YI%M9H<=Q;2k@gNM_96~x*#S!o) zS`~X&ZMNvU*TlwF?PFNf3Wvo79q&8t`bQ2u_W&Mxi7}e_(*6(;Ql-Qk1PF{76XMA6c@(03^erZ9&|%wihzn8-|~GMCBBW+rom zh-%^5ssKYtr6dnqf@P^zmVyhNhFD7YfGG17q}UO0K}NKTH4k@770{;xIxq{T+*Kvb zgoXhP$frIpK!oqas~|;m4Te@Qgdrr-Ae?;H_6`C^MK&;s5zL+^4cW$4_O6isutPUJ z*8yU=DvVz;Cm(Ny5I~;Kl=DRB1SM+G-qo^7V`5kX{wN4hNm3D6;T-2^s)SE{>eDz0 zWoeVDlz|y|XpyFrDNSL@%H#3Wr$7CY0ZUfPk0Ohy2q}@J3X;>@=qp+nb*5OyO4hQP z^{i-3t6JB}*0u%;nn#i5+N!w{VQKSO2&u;mob}a=hB8w~=nC}Kr!oe1&z7yJ$E<2a zn%_XNo(sf~3>LyV*Zsq!T4d-$6B1O;P|qN!qm6AAiOYf@lA;C)Y-2>#(7f&yRSHq9 zW{7a1!6M`abrC7b=#v73zWB|re)roa zxGG91YMX1@)_D?&k)WUm=B2nsid^9;cAEt`l>)FgC65{;X=>y`L2^)+$702gEfQ1k z+(#ev7Q}j)Wmi>axl)y0D_eAl*p5g`T+>d}Ivnt*^+0?@gy z(~xfq$;RM1^rcJVWIdQTRfm|dL?`2{U~*hN!6b%L@lt|7unQ(I%4N!e>FyZiD3B~4 zgkKD>LE^?dT&ps2R4U+$do^22s2!BQfDW{v2TkZg8~V_QPPEwkTNJszaKI`l5p(m( znF-&h1s<{k#Zu7E9_&a7zMAxRuSEU^E9`}KPO^h^er43d^!9-dCd)8@upnSoiJ!gs zjRMp+j2nDlvL)KJ6}mtIQKp!@(QUD5>9uPl*kL%5MRtva>Llwt%L*FBiUWQ?w-69Z z1eng~rq83ig1Bfx&W4X)+&LH-*ntT7Nk9j{vg9~6d7{#0>aB?|3=#yeA)Wqos5Nz} zy4{TkBk-fOL4fTPXq(%i@ivl5nO%tTv)})Im#ibKpMxj7ATw;Y9dJOe&9bx1RRTdl zUqAw932ZAN@bo`GohgHG=GcnPxz2aa^PckWR^QLzNH}ePz|3 z%YYTWL`4yZD$kjGfP0Bh>;BIP@|G&{90o$|t`c!6HtWgc(OmR74^*6tEB<8|3zBv! zF2J4-{qbq7vjOvQ$#86MCDc79^`{Mnv425<-v#rydN44Pg%8r^Qfg*ut9ZCf_08uL zAkGGVwCX#ugK083I=(Od?gfc49Zf^#&1v3qoImeBLcZmUj-HC66Fn6l!1_9$+`QVf zKoEljdK)JI#6nNL@|Vy2<~#rS(2xF{h&~iYBRx_nA(f>QBIzEe-~kbO01Eg+h7UAn z0U3CK$8f*_Q9ghKUo;3AQ1HkFa6ku=2N(;8^Bl_m0|&L{02=ZkAD}-OpaLFc0~bIa z50C4=<1bSfKzGkODoFf2_o0E9GP~hE-eO0os-VKc*1ncYf)YEyIukkE8$( zAOay%?7d}BUG27}4J)|2ySoQ>4NhH5w7Up{w=8qR1nC`q3#x2dG=<+%o z8C{)%-IS(OEP71L0*5D3J0jI1B0W7Kvn?VUjxNN)$0o=p2QPv=fdy(%thii^h=wy~ zEwc75vK}w0kvHm-2U?y-IEW@PJ3W&0Etp`iQLKxOyMxERSvz{fBYG@7dZI1*mupmX zV7ROndI)dKl6K6BN6cD!%(6D`Mw_^%Kji&F%pPy-`dG|YkJuk?V(1}4B;#XG?_$sK z;(qbQU1`VNc*NbM$33*gJ*~xo@8Tfv<3W7!P&)B2p7C%Q@d)klNbB(^_wi`>39IE8 zGk8XErBPXU(WWH{Q)>w%_X%Y9i4=T^KH3Q~xRLCVi45(DOzVj(_la!yNgNUAH1IKa zd`Y|+N&M|eg6m1b_euO(N$u&0qB_aap2=@AlI7Zy6(U}XdnQP(C#&+MsOzL?dZuV+ zq@Z0Sui~cY-lrJhr@rS)HPuP20gt87$r_orr`oKi+TEu*;HTM!q%L8_IC-XdWTbhu zr+KfZg@~s`{74JrOAppb5A{qBZ%6mHOk;VA8hf7}kDrmqmyxWKk?NU|o{^E+o{_zt zk$az!kDpn{mszZnS?ZZto{?GEo>{$~S$m&ZkDt}Zm-R^}tHm>`EhDR=J*#UytLHwe z4?lZ=FMCKQd&Dz)EF*iOJ$q_Bd*(iSZajmmA)TiJBQ7I-EhA^6JqK87>hF<5B$i{~ zi4l94bKsf#BO~{yJ@-Z>cki4qix^A}UB7vVG%{HlNx zmMd1+C{}tXR(S}!&M%gUEW%k&5qLl!WHWfXQDX2=Vnk3HmI_N!S)l2ah|HfW$d80W zQ)>56>OfHDRH@)*RD=~!j5<;3+)?JeQRe$lW}sFk-+|`Ak3qaqYSs)*=PzKWhRW>^ zqCf*th=N#EO8vbm(laYEGra>V^Cd^~12W3O`O0d%D>8RPjTYZzMhu@!CphN##fz0g4!Qy z?lS8hI_gX@Yt0`j7$Qr)>eP<8)WRo#==xqUFVt#Z*7}vAag)_N@i$=UHQ?ycfeFy} z2{1-<(FXgR5*R?)2WYVf6^Rp;jA)=WXY?T&5Klcc{aket|0@#bTF-jm`=KNqs3w;O zaeYLC;{i$LzoH@oC0W&Qs(d1612KOopzj1Z6nvuW16`!mHhDFRXQ9F9p?|N`Qq2M} zQ4mss$v$z+Ra5m5`dyT_@Pio8T9ykNH_#gy`$!G*LF7>&$4}TM@=y}-Ab0DUm-t34 zfhv|sG<3u|+^n|9&bDYmEc~cKh^!(;{`w(tm_`PW31I~#n>Rc=KDj`PJYyB3H99k5 zV+LadV?6VBhBgKP7LJb{t^5XJlc03Hszj?-%toJDK6ltxfzmoVsZ^lJK6EzAgQWAD zkp$4c%XKyjR8gHPi$&GU98;yrx0MNi-l#K?=YymWJ6gT)nycC!vAUbRI=&nT=j(Mr zH=$kgm&ZKzoD=r``ivg;h(_31N}`I&5MO;N4)fRq8!qzd|&#Y4t1*-`bY=k8!7v2Hw0R^>M5`5 z`@0zXtGv+k*K>I9sUVK??q+jr<@EGKR>GPmalr6#gJFg&=8p5M_Oj z@LYA1Mi0Gzi;N&B%}Ea9Gn#_RCrQrU_kts)`Xk71dr=&FhbznY*eb+q&@@y&ary_t zRyRRILJ>uy4{`SoAnG1IXp9knQZ-N;BdffF0hx99oW*P_*njZ>s+nJ3PB8EXM}?*K zAY^tIMnLuVk7)A@AcU&c@re>!CQ$Mw;bA2{-N`F)rV3;II>X{I!Dt>=8!#HmXS7S) zPV=tGuFc8ws1f9X5fbBS<&X8`Mx(6Z?Y_W8_tkx{)irgT6~-S2CpQNv>U*?Z`sDWd z%buWSz4;I2xab8YBh)@6M~{yQ459{9Q~7(8GmQxmfsBdTTKV6fM7Qjpurssup9;Qu zchc`1jrMbLIJ^^$BD>n>WD=o!4(Yk3&zo+bvMBixRa2lXD60>~vG(G&1y2Z_`Qrq1 z|Eme6LBi+SQ+=T>!cWWDLI+b*dz({d3S2*;(KwOO-&s#F9`_eSFU&om&Sy4XMbGot zPQD1Xcz(ox=RJw=wuPc|9{G7mmmc-!wRqx0MErG8<7EW$DPrU5!;M8G_9a;Mm14aW_={Tdpc$GPw8P9r zA6wnRpR2Al-NajsgBpu&H8Uu07Z0r{Lw(+#XQT1@x1%6!SpFQBLSEv^t~M-Q%BQYU zP*|UN-c0-%KUag9nuC@;jXu)RTXuxT##TX_16xrux{;0cC^-2;aClYURO+L2=T@V+ z;(VRrEFGGlnjolJa1&ANbE{Cl-@!Bm+FYj(;dSR9vD=X99`jSLN|KthyhF7;`GRZk zP1835Ll#R5jDc?#rOjNg`;)v~zb5}0tJ7($P8ur#~tIG8R3a>O140y&Cg!eo6zW0@}CE= z(rf!C=Ywiiplik`Aey1sp=g8+e&qas>G@W@eYBAFO=1^(#5n&AreT+0z5&LjyQPyo zQUUZ{1&v;*Dj^f}Sha!qbgZWEO;qy}ns;}$-r@pbPvQjV`U|R%&Ee_K1^X-ztWD37 zFB*MpJ5~L{UH2DO6|BRl_P zQ=O5g8dg8)3cm4(oj3NJ6GI)t%{n8*(W1Z5z=>SIzntS)m+d`#vy^RbP=aBKKTFcZ zlYsngkMpZ(;uGyuCk?PV>v@Go^iyV5|7gvOBwAB&7VwexkF)31dgLa*Dm0<|Pafby zSn}`U))xfR)rm(vzGpbo;^N&iV|~ z*YB3i@lMP3JV-go9J4wi?;>{kCgEcVG3mkb>9=qS^mgKRfjuWhb?Ml%x2WtZ35Muu zf;)1vz#YNo#E{p_2{-Aik0xf1Y42`g&+aJM@0~<$`|BQ`p`Y*@9$excsRJHjW}p0D zo|NeCQdqI(sO~4uDn|S;W?pg^MRL4*pILVwSGJzV?H~5?M!pz=T~tv&2|qbNS4JN` zhw&pd8s4Gq?(~FBOej5^F=LF?p+PiI3`P2ZU!I7sv{ZJ*Ly?KWLF9WOV!_CU!J-!` z`_jSiJiaz>Oo!ko$P{yz)-K2fW9i=Y1mmk6D5NovZm5SR6^=$iyDqq_EqtHIAqhL6 zLQp?kND_BvaD8Z?Rg0Ca0L6G|9BbCT>-L2v)I8B{G@H(q%F_I)+hV)b6GDhvD%auq zb9*>T%if?}wPqokf%9m#E})q}IGXoZZ%8AC+vbUbZL2gP2>cV#DLu#d`r!0un*>}U zt!ibqH1juj3~~X4$0{BsWs)Lsco7@)_rd64!qRA)YazkmgvPj8Sa45w3fNRpxq!~3SGMOwMMP9JYmtBm!=Ma!@jfNb$k6vBklFw(fsK3aQ$;2tPtFOmc1a1edAouI{#y}==>W_ zpaIC5Ild&8Gs2y~a zoq{$_CFCiUwZ+GDMGJ%drPYlRkn2_TW%o#f^`&sLh>i*r_}(1!Cw)(~6x4&+(eh)p z)r)dMkCKbB!aGxo0z9ZvTyLdt%|5-AAr0YLNJlCDq$HKX#Dn5UHK+h-Ro zp))}(Rr032UsKxb@6HDgA#Uv14I;dcR_QOMaaGV3nd8+z>M=AL8(k0cn#AxNw3)<- zz~J}F%h2#`J{r8!Qc><-)=&yLj=@_Qr8D4NjuOdP*DzdmZU2rc#u2BS@u9?ZJjeEC zy)<**hY^(<>mpiXE>`ia6ix;*V~gOJ%GY)l?Dy|OcE#tIp-GZ|;e1c~VAG`J%;@HB z=}|BgUgzFxV71L`pR!`zs|GeF>}ug8!M4nQA=bCzjiYQ6QJ(+FH`o~?%CuQ8hoaNc)9}M=r?>_XG zU3ac!yW<-CN~X6tGy3LduJc9DTG`-gpZQE4-B?y#pS3axINOEWANX^f4J$*CGe@v) z`;7`@9&jLiRixSDJNh#5qi9?%_tKJW2=2KRHt!GHF{5DoG@D7G2*2~Cd-zC5^_x7GSm+NO=3&Yt2nXR@aCvOv}dmt@ikd5mlgwVA56f7&f z-oGdMxi>04w=N&5H}ttcI8AKF><8cDBWbnUSj!+gGdrv|ZtW7>P<#Km=O81&z^}Ce z1f6rUo+k3W6WS&W?uCT7U{fk;K3T7VxkOl1@|%M;UX~8!phLY4h!hV=M@XlHh*!ig z__8GhG4mb_h=ufFKSp-?6JmUV^a*!VQ3bdhwUwWC4T>$A&0@}8Ya$KeW~Yy|ogYUb zAW#xeYD#nEHUy$>nk;DY$yst#yWlV3qgO=8y>j{$8myN);g4H}v$A04#q^a-mVWHu z;yg#P$W-{cUD+3WvzSNLX)0P~NeVLqAMjOyx_xF@ZYv_uxqs1|MLt3~q=VhBK%Mq- zszN?uuQufN1h}pyQl*wR3fb_S`qqH2d7jKVa+qjNtolu;sapY}UZ16sYlm9y4KN*r zwo(biz*KAB( zuKplpt-IWz+0wFHV;^Fz|BYX(V`aJ4ec0OInn69QFd1o1^-wNkeW9`hrgTK?jSbc= z7snhIQe23QDYbykgyu?P+OUoJ>rR~+x0R+mXj=;@0o{evl}}|-wpJRQx+^U!&2=HR zHf92P8!OUT8aq5CQ!?oYl5bDLbc>PW=P!)%MvCJD1m=wJqo= z>I1JyjQeGzhH8sRB6DJhhXf3MrLJ~;m$LU-?lib*S?xXxvG@KaVEC}I+H*T>?|a>8 z2)mUG1j%qgyeQ@R0PH;K19%z0(X5( zM%pPcrOT9tXMJ2b)G4_{(2PNAeL`!*DYd!FjKzI@(h$ZueOS<(BW-=^gS2zza+f)G z>pFPaKGZq;o8Sk2M807Rj#M|pjly*Bai<@QOFp!ag*fiUT$r>=A$GTgG|$F-T&PPi zwUDKp*2Y5Gh)e0~Zc8Qijm10|*K#Q#E7i1(r7~&PN{wzS&DM?Ox=`0@Ga+l;)s2(qVp|T!%p6v_o-r{}gyxG3arJp0& z-UqLHJ!jmvFY{o1^2kX$=h8@AQ&?@r;qARvTDPz3!hBB546o*U?QUBAu#WViT{myH zZ+l^V&qG9>tXH>Hhh%!jJnioLO@!}f!+ftwM0}2vcka@weQy|LPg-~kA9uj8es{wn zzP~1)A3oCiJs5s^S_+#TJ__@D`X=J{u(|`jUF|&eIrVr^Q1=j$jm{An`)^BIk*NnB_;e21`!knxm^Z% zf(Ln%C7?6=p0fr8h6V+f28F&#%wrDRI1dP84T(_=iE|C{>Pz4V4oQJchooJGWQzMG zgNI~`hvYsD$=is^4h<=O9a6d)Qcf0Dgc^QJHLS`ttmfUtFDa^SI;`n3tQ9O(f;p^R zJgoa^SZ`=pHgj12>#)JqupyN6oAqHMs*(3xBPNm~KOZF!)Q1}2T0W?MGEE+_EFQ5! z>M0X6dlYbNxQVo9Ik zw&cvr)%33E%z?{{6_Zjv@66ZYnIZj|??W>uOG^6DN}1{4nIkCWX5e~Zs@Y#$$|@%_ z8Fw@1rph@)vzNiMz*bW!1?3Fw*_)x+WbfJgue0DQW!~!9^tRb2DwXKX*%!$h`$IYj7rH0*hdDV4OfITWe4E>CmlX7f0% z^9q8~q9>7+IgA5LEQ1*y6gn2fhrPf*z4#_&Np`wZ z=s8^2b`}qW_7W|%x)YbWB(e-9-lEqWB&N|a_76yv=C^N3mUK#%b+7s5 zx8M}aWTa}su<_K?nAP7ZE=o`@2ZlpVe_9}SRnRkAF?U@l9@L1LTXuB`@GB35%ZIc^ zUP{dlkQxrg$5VIU3LxqW_6vW7OSk-l1+_BoOo_M#ue z5_~v>;d3pELx5eJ}V(+YoS}M7CzyYilcBUfqr97ABi zGz1Y{xc!zU@MUN0@NJB`UOQNS$>iIcrJq`>ih4nUo5RrCU$D0~t@YWdbZ*DUT zW!sh9+6ZIY^HVWSdftZY(Rb8Pa}`vh@E<*{F{Yu>+kjqOcUZEL(soiej5+y2-?A?- zvL6*i;UhV1ZW_smBQqzZr3t3~dRFqKCp(~N(@;D`Pcp?Hk4%kkMH`bW$dAn=@;Qv+ z$5ub`ofo=v~1cLn~(c8!7k|3(_PZR@VY2Y&I3cBYHW(q>7+Py%f_k}<#~jbQ^JG7jIq z(vXbtyM1${`G8it7Xo4G$gFiKq(S8NnW;sW;BL=e9Wsp>&N)4R9p!77$Gcdr&!O=L z1u+oJ?PkryYN2p@eBAmPEZ;-TwM%W*oHfH;ZbtNREcD#eG3h|gbn>P+E0Nrgw0E_X zMjAdSqkhzZH8DYCYa?JBm^Itnz<_6SJ{Gt(+g<$fuYUEfQeigKU_KN+D~915nTJe- zr2GiX|6y9ABLrMs5O#;UXfXQVdw+ot}psqJweOH%3} zw%}3PN6c2|$+i?(EKf?hJ{G}bPM*$Kv94Owt7D2$obHKQ+Hq+_`#qX+XQbN2eBkxq zdX^IcA)7r#n^e7%XEB0hH}X(D-c2y7!{^vX^q=vZio4rsaSb-pFru|5Hc9Nco7`Oj zg$j3Hiv2o)8^OVL!IYUN(mx>*EHa#au1e3Z)lLR&oW9|}dTU%J(s8;AQ_NbyyKZ;( zt8^+(P56F=x|Y|j!@&Nll4u>#)N{|7)&OS1r!%)BgptxyWk z1C9wSC(x+H7rs0YX?Es?s68l-Ke$gp-_Kqy&lD0xF=Wn9Bkk_e z+j>#>j<@3pm4CwSq{C@hBWTSuYzvIlatAg2L?TT}u{=QwpnM{^K#5BZkR_XFDWAy5 z3yk6s->|*v`5AHFMx(`xsFP(eV6&B4b_jnN6-hK_=LiEOk5|{i=YBcW?ugWj43bTM zpuco`89^QHgf&T^Op-&S^j1OjgPSt@m>nahI3kjbGUWhxAY5F+&kCr zMJ`P_5%Tu0>fD0e^8;T{{JxVqK3u~^U*;5@@rbA0rnadM+)_325`gaK+MN>Y?Wn{_ zEMY0YF-Pvl^LLV~@quS|&Z_RtdeQ4S?s?(Rgi8NQEx_N*tI zkUMbnBtNVi!OO`Vl`NXnTSDtWtF)Ao)-j6pv8$J?X!avZM_#?XXQe%*h5VhtPQL4} zH0GU0S9wQ?NDra~DqBRKrm{PuR*#CXht$yw%E+|T;(KF-l=$4B%8Mm&#SkRBNnW7pi4bROsrrFVKv}!Bn|zSu~oAhvF($MteCiuV?G6x-lbS7p>QE zU59Es-kd3Qdz7ujeAcwmEY^ZQ{d4hDBCWhkGVpbBruEZ1J}_~ zrB;*OPcK&}fx#o{TIO4ap&s8^92%QPclk)W^-^R5!K%|%`nXs26Z2JrU*XO&HCT9U zcG?Gr3(bVJirkJp<4AF`x>($|57HK)cMN_$p82~M6*C(8Ts}SAUY?!3i1(?kt79djAm)G+uucs{BGDFV;0@ibrAKSwv4@#MNwFNYZ?$vKAS zBnO&>XQ$q3tS?4w9=d&V(aspfNFDaP$B4Qxvo!R;&~({Lop4>I>j(kvqFMy16bt z7iNB8h%wM16S4fMN+KNNI^DKTK)Y=I{;;7(9TV5Joi;?#anbzc&{5Cy<=7?7XfSGT zg9&k}`LlhN(CaS&h{C+PA%v$ELlKN%7C(bWSJr(tAGz`h)5fsd#Z@~6_a!goaJ%i5 zl1|&HZPjXx@5v_H++WE>teWb2k2Z_qh7x!2=i85aK3b#4H<&JjrS;@`)GkJ+pL?cN zdb%dWMR(b|?RwE^9mQnwSF9IJdGBmQb?&+aa-;fIYiW0R_!s*x{z{hE`Nn7(kWo@hEgYm z^BLoU-kuIcrZPaWyh~PpcCgb>OWV1@=|)0v^l!tUc-Z7ApSFGLeE&#+H%K?ap6Dq0 zBw$j4piT;vcoJSGzWqS5Bvum;8%h|I4`De}aXfrvZ?sN^i5bpMCU@rKhJ}hnt6hS& z7ac5nk4`vX6ll^=pO~tU3Z-x^{w1X$DffwrPJ({oZDM0`sUS5&Q|O2WH(g4tw;3%y zeW%g966a7P6}kc%q?|!xT90H56onCp!X{C+9xO=1kyJ5d#?h29<4waQGCpCc#Gbj* zRa$7uC$7r@ooEU-h}!{Nw-eu#eIQ87zgRKj+SrtH>P;)SPe1D(d5LID_KJ`gt3jMLK;7nu=vm`m@q763|!^Il+Y_VSBh4y(Yby8nxJ;nfFE(tk0LF^ zpqf;eS-IN%tGUkC2VA8&&#=~y(NZ5O#Pt4^?urIYOGBNy^$dwF zVS#E(V`?{(IcL@Sv{Oq{?lY5x1mgxCf6J#*A!aMJs*Pn1tA-~NW}87c9hs4qmX>a2 zJLjs;Ta_!#==;nLuXyyvVOrZpgjk%Csy4rQEwm=TXK@Ki&(DpVE3k=SacipD`svi# zx$PS1jD59r0b|j&O3muEShaoK*xG&S!|FZGzh$7|ULz32>U&wW^Zcu|7yNARF#XXG z0<*0TMwl%Sv-%5kr)pobDO)fl(=NPP+W=0FgELX|F0ymmAPJZ)T;jwKIjU`lMwmTP zt$Gg|HFb!|mp$5qX&-;FZDc_m4eUxNt50~@HYyBej}Kz{3U2cllNRPkOsf7$4ck7Z z;LDL*#Pp3`&2xyiv@Uj5?;Epo`=kMwBVFhCD_c_gl({fx=3@2=hD6A?X$kysJSmpKg2LQ=0b&CD9RE|cu?`?V|}@bDVZ(lVZ9bod$>wD zYYtJgycTo8T;&qXR@ZJ`OQpiEE7fX_wj0-ho6p5ju^Nta7CTm2dYYB6i^ECK3Z_4+ zgQ|P|kBu=q*G7chIIypv4H)~E`4d6)KKW3lYMmP^J=`sh>15P*{%~^f;y$iJP!tfm zuMOzb{OUf*8Qz{F*bL%g!C7cT>3K@@Gtg8=GKZ-T}PT&3E5qo(@IBPF7SVQ6Y{BK*ozq9{{S^&%YV_-zB_E$>N=A70c z2*ryHDqKc?uMcJb#m@g%>Sfn=;TOL2nuQR;6Ns;0l*Yz>DqawhLH(-%57fkkox*K6Mi%=bm$%T8y4pp6(DPdy|`Yjf(_db@5J|S5c1rc3AZ1Y%k?UT;B zcA!Fj=j#LNBJ0cjVlVinR|I@n_wsz%3tR;y0s)f`fdZVC%ZDT(1JUGzSjb*+=f4sr zgObjNQX+%a%!f80gE7yCu_J?Z%ZK$PgA2`vizS0k&4j02p?bSuF1CC3Xbz>6ivPc6XDB_}8?AgCoLY$+h@AtxFsAetd3UMV2n zCMP*4AUP!`y#bc^l9Ry{lA%zL;}nvUP*BhmQV8#gP#L2?K%l{i)xG}wS$1e2cs~S^ z9NmTj9WH>@mx3;|kS>;jKDCfOmx7_RkfD}>v89l)hk|LOkZFd3d8Lqfn}X$_kmZzu z^`?*&Ou+_I#D+r2j#I==Ldii>#KA(z$z8-LOvxo(#HB>}TC?c20VTJ25w{&Bk6RIs zFC}m2K0SDl)@hGQT$);UkNW+4GqwN>2y%>}BEcC-p_L+`ZA#&TBH>d?k((kBFr_F= zu_y|dN(`r1jD$*@rdXVXN`kvsLYPWYx>!<)N=ma>%79ARyja?fO2(~N#+T|%Xz`m^ zD%sRx*<31Mo4Z^sm3&LFd=HhvNU_2Ul_GFi@7TTo2PFS5BdKZ=$^bnx!2lH$>bE#0 zZ%L?CX-ZUCsMWYj)P$+krAyS6s5LZ8Gz_RU%}X@xsI}Zmw0xj)R7FuWSGH2lr z7T{MZGm<8nWv&LaZsuifcC_wpW$wPT9-(C(v9zA4WuCdTUZrJTwX`2w%0BkcdXJQO z&(Qj;l=*Da`W}?|p3?f=l=*>a{b9=eQRo72$^%H~0%^(vS?Gcg$`!fjf~CuYmFPk= z%R>z4Le0xV?dZbX%ENr=!b8i$W9cGN%Oi5>B1_971Ahe7mPhr_MURw6&(OuJl*eq- z#U7N$p3=qLl*fVT;$bS{Cq6`>R3wnlC(=|Tvd|}SS0oA3Crei(E77NDR)AAbEE5bW zQtjx|+$z$1>C;0i(qri}QY$iY=`&X>QcLNxS}L-7=(9&EvS;XXRw{C~>2nV%+-fUw zZz}S@^!YHA`6vtpIF$t?422`~c{B_~+?7Sb48_uw#Yzk%nw2H|M}-EJrFINuZk1)e z4CSGf<*^JEJs(O_87fOFD{C35S}Lo07^+7Y;&LmiS1N0^8EOwIYfl;KZYpaQD(hgX z>QNXQaH<+e7#nG<<3Ux8+*M7&jGv^dJ}EIaYcjfVR5hDdwb(JXx>dFMGPZ#jT0$Ay zQ>)r@89Pd=I%*jsVi`Mo7`sNQx@H)=SE{;&b33=IdQKU8Z>oC1IgEX^Ry{CG{W#VA zBuoP|)dPWteJs_3!c0Tb)k8{5!{!!)noJ|+)gyLHqi)rsn%@<@tH)xQ##5`ub8Y%U znI>wPCR?f}dzhwhswPI5rdO(`x0z-RYzk+pXKt!z!Ax^7HFM(hN=P;HB+LsmH47{? zVK~f-!puw3HA_m&%cUQb)R|YzYgX)-S2=zzyD_hY)~v-cuRB#MK|w)~-$OydLcBmQ z`9RD=2t#l{l)=Ct!@{A$!=oc0Vj&{oAR*%-qu`^U5~89JqoR|bp_8FukfQ^^q(H}{ z{3}%Gm{b^lf<=Y#_h4dE1HraW+EbCCL(1fCS@TZ zV$n%RURMZcFQSE9+)!n|d3&YCHQLPJBt!otGC!y_UhA|oTCqN1Xsqhn%X zVq;_D;^N}t;}a4R5)%`Xl9G~>lT%VsQd85?f!E(NGBYx>GBdNYvU0Msb8~WXb8_?Y z^74VFprD|zu&}78sJOVeq@<*@w6v_Oth~IuqN1X*va+hGs=B(mrlzL0wzjUWuD-s$ zp`oF%v9YPC>C>lA&CSh#fVQ@_wzai&baZxgb$567^!D}l4-5_t4G#^Ej*N_tj!le> zPmWJaO-#;AP0h~C&d)C_EiJFEtgNlAZLDu>ZhYSQyt%!(xwE;ov$geQdwX~1%iiAJ z*KgmxfB$iGbaZlja{BZ1{OtVK`Ngk`UzZoZu6_Zz1akeK9B^QW7kDDx`W&&qKSz1VC4$h1fup={$%lflIj!_P8n#B_ z$OHn3GTu^+C!r`7>Sw4>Po}-_8$`oj7EfhySzC3DV;0Rs2zi2w@vy$DL`cPu_@uiW zECdRhaL3%KS1wkl=j{yy#0Eq`IWF4gK8ujy`qn zpe{Ew(xb`zgl#rY1d;lc-zTHHbEU2*Ldq43(#CI->FhGxf*DQ+vuR=+2l|u}F|02W zDp1q9pG;LQ1((wy3q#YFklSO4eA>>tP%56pHU>vu*pW?aE{lYf z%8^X!BS{SU%bf#DGrWet(|DYkvd>Ng(D3`mm`yU zt0d8i6%wHjGmIJ~DX@N2=Peg+Rbhuh5MsIr&xeElC+rnK>_fyuBteuxKtMo3LV`e` zSFc_{K|w)7L&Lzpz{0}8!y_UfAR{56A|s=tqGF(-VWFdAV_@K7V&Y+9;$vYEU}F z7Zj8b5|aEYQo_Q&BO)RVL{#*5#Kfe<#bqQUWF#fuNK4Dg$jHmeD#*zxDkvxc0Ul*# zWfc{bw{PF7s;a7~si~{0Yib&5X(I_v6r>+6TSGl(%TOg1#iG&IgPdS7DvzS7vF z_PuHId(%!6vtARkVH2|nQ}a<%^FdSdKC=&9<{w(kEm}TUHd$CUSXk9qT31@xl-k%9 z*xKdT*{3-;COSDqySRk7x%qi`czAkxdU<($1fb{r-;2G}v^0Qxf1xikGb<}AJ3BiE z;9liJ3Bi!H#fhqun4emWo313 zZGB^7Dk3!Q2YlG|3e)9GX<}%Z?3Oz zZ*K2y@BX6T!`~?Q4-Wnd_Ed8Jj=jHaFj$+*mx}yrgF!;ka1inD4F(u{GM?1H27~qs zs^1$7C|u~9N);2i?2k5O8={r-1cMN4@0#f5I3$vOaZOy6FO;z<%cUSRTa8y}i|KA1 zu^+3~8ol-!CTOW1YEYl9r1oea+laH?FZDs>sokh^`MklLwR&vOQ?qQ||3~e!ROp-kI&~eR+X^p#$!O#Ig7i07p~)B@l`G$Csd0 z<1g|b@u1juz3}c#$V0&v9^_v5Zf)e@#F2N#p5&>#6p_@G9)%wCEo~Ih%oBHe?(8jN zv(daOYwv{x-O@~A?x5PY;$O#_n7X}LftyK0f1vxuqoTQL8uRR-_RU2rbxm3L&ImI& zo_S(%57nYH!rUDvnNAthoV+$jw{J@pFNi=lI3T)nLZ6%C!ElL+Ir+3Wo8S2483!7N zOGZKn0|&+AeZlS*D|*F_u2340dy9+!$}ovSJfvoyX3$b6SM(ZY>L2HP1^>@fB!$$2 zk%n>Vtu zvT|~A^78Tu3JQvfivKPZH8eCdH8r)gw6wLgb#!!eb#?Xh^z`-h-@SWhU|?WqXlP_) zWNd8w{{4Fs6BAQYQ!_I&b93_#A3j)ESXf$GT3K0HTU*=M*x1_I+S%FJ+uJ)hI5;{w zIypHxJ3G6$xVXByy1BWzySx8;RP^!p^9k_x4Gi!L3JM4b4h#zo3J(hcR@g^H1V=}P z#6*U~Muo;khsH&R#m4{%Pl%03h>J{&k4j94NlJ`OPKr-SPE1Wn{$D}GKQh?X)(+?& zAb&$c!((G(0N((t0Z3b1T3%jRU0(-S1~BZ)m)-rn{jd994*J#5@vp9`h{Y+b=e%? z7E<+cxOvO1iRNR?YS@YhVtGv_?NBh>gI<=V?dK-=#m48>RFPK8=!+{XoQ>koTyDua0l=8U>?Z0J^(|UWVz3KaGzC;v>V#lY$#d5W(|CEYEu7UeR z5U(jbx}+&ZiO5Km>pFMA{S(o8sew)6VryQRP z_Imtz(Ys2T_ww`rp~=1F4>bX+6)=U4hxRXpk^-_7k_#gJKQt>?I5CWprK)+qhn!U;9z3nVqxK7V*_$UfP+Jbi%W!yOZ=Bpk>KP1&flF15GqPiQYtbs z8VU+pN=kZaY6e`Y8RIDjxSbFi>*0%2w40>Z||#m@ek zgM*us^KX{L&o3k>C?YH@CL$ssA|fd&Dh1e-n3#;X_?thGka#00DGNkON>*B0?#-LO zQRIIcL)_fm+&$erK6-fhcz*Qt`snZF9q`d7@S|^#w{NhwU$Bo~h_8RBU%>D92ZjX( zg#`tN2Zw}*hD89C^1oIn|CgB6zb%yQ?d=^vsq6%5D2Kx33QXO#i?DP%sCF z0MHB%kBp3tjsmMvFRh4Meo{r$r~%<9cw41s>oqeF=&3IQjNLsGF63`fD?K3LPqWJpI~u}2c=%uxcn zp`bGr#xeFMQrS>@qGi=edNX-nrl;1O$dxmN{RWU2lBwqlftOyWD_knZN|ji}V=^5W zmV-3waHc9<$yRG$dfc$9Qf%JFSS)5jXW~%k);sJ^P!?nq6cm6-XlQ7FwP0XiU}9ooVPRooW8>i9;Ns%q;o$)WLqI?P zNDL7X5iv0_pfaSSq-11d|4(TC1EKlvnr{7{2BW{x=-P=wjIDDJRLNgf{ z8h0+3$q@=#k+e{=n9XBLAh4}i+FK|>$PwMC!h>1yxi%S8KkdT&?l#!B>k(Pe*kLvS(aPj%SfD^#{8&DO% zQUF5%^yH75{1%guvC+}rAo%;h=l8S+7=ceu1DTtd0WuF{cJ@yeW@rDFxw(bEXK`+B zaen?!mgeV|78ZakFD@=GEv*1q{`(w#V*?ly0Tu($VjsW+00=-|0CD-l24Lb1;Nc!n zkiQe+&*b|*8GiqP#LLUezvcwR)o6V|UnmMTFa@6{8;ZhZwA$28AO{v&QhOoEHh%3* zpi#5?iY8hz840P|lbyjvGo1xvySI7Ink$ei@D`Lq?@~TlD6K}JsOv<(7_XG)8_nQK zxm-#7Z2a2OwdzfcK|vt0fGd+u2;O3*&?DQvY^%-xkhG|oL*VcZqRsemK0OlYk{$LG zDh6$5>Av1ssl6VEjdAC;?QF4MoPM_7KJ4gvX@3;rx$AWs+9!AkW4T}d_Ali?EQ8 zsIag&P>)1Jr2ZN?%K!~YOzclXk`))1m5}%yNl7^%Qc`l#((-?X(DL%XYtp|RKmTtX zwESPdn}7b)?LRL`e{V>Dl>8}3e+JKgwWFC?fIt9yfT=S88-OuDM*`^bZ)?(DbLihC z379_vIs&-JUn26`Lx6hp>lYv&09}5=1(1(_=*J&^{7?M*n>U9Lz)L&dm46RfP%)U& z>kIk=p@{iS@!l5pBaPyrEYYzK#+xAUaZjC}nY@Xjdd~2s`(-qdNT$R8|FQShL3MUp zx-Sk3%YxwU?hxGFAwaN@;F91ZAqgS4I|PRi+}+*X-Q7L7vp6rw{=WTn_r860_wCd7 z+^U{6Dp|E^Rq@w+-!Y!&ne*oiIyDiGe+JJ|&v&%k8%$x3d{=X7q~ayX2zkn2_e#x2 z5kBcl?U_Yu4m<8m;M=fynSz%E0p~o2`kmgqfi`fL-!e6$&+7}EYdDQsoVEw82`Sj9 zTO9abpyMmX+Dx+Zcef_C%zMOksg4s? z2TS#l6Br>|QS>KU1R}JgkjbLQpv)u0NWOf5fXuZFyx&mt*HnV_=(#6CmB0`| zG5sfZ|6iw)-$MyL9v%TufU*kACI5-31TdZe*Xhx6;^O(`ISKOf3j>CekdTD1u;ee3 zNk&u@C|OZaIWaMyESVWi`?Fw*hpzdt%pKo|1ccKY3j z{4$>g{xqL}cI5Yy;FnPHTP67i63M@sN`Pko&}0BA$)gSRYX&(3oF(8<0N6^8ea7FO z1pmJGy+?g!wQrf%`3565sj+RCIv50l+tJ#fPL-2%fB=OYNJ3@)2GV`k= zj1)p>R2aCfFjG4to>)4J^KsA)#<93Y^vk}ZP>Dw1Br1Conc0LY745R`KfW{O`aFw* z*pJh*IbZ0z!>gV1yzu}5Luo~w$$f2qDML)FJ??|)I#^EabFKDi@h@($a|wB1Ci#-j zxCCa?A6Du*uy!a+5U*2Ay!IGa>w|_9H@y-uB5oC9Q+Y>WBq+;q(JOY{FJ=qoG}}4E z-opiMFBZ&IioQp&K~tpYGtpV@(MH9}(o5G`f5>LK<%yNI55*s64s$~7SazL@r#5Fw zU9mdThb`!$qC@v?epjuVPm5$ao6Dh@IY7#5g-mNf^Vhb)7ib$8pcwuO9vuH8XZ)xA znSb!$@bPi=_jd{m{16mm9};3767niE^hIc>a%iY*XsCE-s8DDqUuY^0@2YTF)>ncaS90u>Pbm2 zlatL-QY=$b?b6dfW@LQL%=FIA4$94q%+LQ`SeW(igmwP?w&5?@>Z6>|-`_Pn+%-Pl zH9g%mH`lea)U~?WxxU`Hwbi+^)3LYLad6OabkuTo)_8ddRL#{@#r1W`%}wF$ZSLJ& z*8P3@{e9Z~eF_Bf0|H5eK;j{g7ziW^0{I4kgh3#o5J)ft5&(htK_I^WbooFa|Dyi> z;3!UhW;s>WD7W5lTR|we= z&MmJ#$WRJdRLd==Im}cEUVpDyZg!aU+A#c!X%@7v|Jo}V*m9E z_EXvpr9LU^pr7_*cGF<%&q0b-Nc*8aUj&4Yz9! zD?K*nI*ZPwVUwLpR<@-?<#Cg>B~2igOxX?Yk*sij@O6xyK1&oW0gpOfwHmjSefuln zSf(^3Cv+O~0q*5U&nGk+USq+SJ=M$lzZ(>U#|Gt}e$D{1380q(7VqBzGJt^t1Z1$V z@PBzYg#XgRA;tU0(HL@4QXm~e`R}A-{?ed4+BG~p0=&GxdlV5tL7+_$|J|toQ^_A$ zn8$tv7)*ZE<6!bf$xBE))~^XgK}zbeq@@*QWE5p(mE`1<@pPsx!)l7<3FWR`6l2BgAMRKM$%-zF}xz+ zkYLZm;KcfjjBSYEs-8zu9}vZ$QTQs)1C4PstGC2N0HWSiSuj^+JL2el%Wa`h^G2w( zJ>etOU>RBHBxpiA9a`j)s5n8ttjFzy|uItv}p^W|2B}s&8g~5a(erziM#S;j^!Nb8J!o!0Q z5RefOQIL>OK_D~`2pt*u2?`1rC^R%obaX5*80S|W4%q8}QwwOFq(np%BqUViCBO})z^^B46Ul$Y8 zv%eM#3lAF`A3HlgC#N76mk>9%FfXqtKfkzu01%P`;&DJWPDVuJ?_-O&xI8ebNJu=^ z@2TXEkwjHN;rXwTL>(AOo&$GSXaM;SApD{G-xd4#KegSHl9GVrZz_=a1p>cWIoUaR zIk^RS`9=8!B?X10g+*mW#TCURl_jNBC1us6K$X{&mDiS40998GROMq;RMuBiHB?nM zR@XMw)-~7Fx7IhbH8i$2Hnlf5cQm(jwzPJ&wsp0&cei)+bO6=a+X+-xU)Qhd?(XmI z=?6AHP`$l_eSN=c2vA7>Qp12$`nOCPn-Uy>-`=K^!e3=oh5d=fzP1T2w#Ao}t1 z=om&Y@5u^cou=^vNz#GLq3ppL%~?J6cOhY z2svkVF`1iAMkfMV&}0eRJBK7?|Fn|BcmrdoNZkt=R=HV0zJ7E$+Ut^3>a zr3m!t#>E;rei0ANHO$>!;`$yM&h+UK@@c#nFar~_Bkb~3&rJ)h_UM9;X)TdpVSZ@~ zzh2$3Pyl7?Z_45yhUfqO|G&B&zb(;6uLB5Z{C>ZK!H<$778W2k0PZKCF#M0>`R_)! z{!i`Y|B_(q|BLv=f4$=gc%6W`d31CHy!HVx;lJv&|G1aqpq?NRUHx1V^?-khBiJ35 z((3yJUObWaIYmbx@XxuYvx5zf1qo zFMt>W5L*YfDiBxrpM1i9DscIK-EIY<=Rh0^Q1pRV6VN6vFE0ZMJkTAVoSgjbjsb7) zpLF&9$#>s>|IYxs@%JD7o9`$9bqV@ow<0UXvbneU!jp=d{6mnQTo58Ifuhccp@Kh% zq7@bC@B4`rstQIGE8MzHH>1P5GFC3yE9Q-hH+%T4>VS?a5BT#??eDr6yoKLVNA7IKp zqLhEa&VNUcWJBZqR`R2TrlpyYg@xrCD+en(M=Se}b`Fm3-aCHy;P}DuBXEks&CSEZ z%g58($J^IGFevm}M0~>cvi9*99EydOX`zR1Ez4 z`1vmj_@bgHr>-!it@vACsqaX|m+=bQsj5#iRbOXoJf`cz#~ObOH)Z!Wm2@=M0olB! zme$7R*7~N_+Q!!EhSub&HqZQK)3j#Emo@hLrLrbc?5rN@w0w z%)YN%aBo=(=~{^!Sp6}yl0Lj#K0MPjINH%S*!6gHtao6bXLzW4ZmfP~vT}8{dS$U; zWvO|2zGG%;XmVm~VgktZ{jR^I`+nv7fK4(zGdDLgzc9M6)Hk=%x41IAuspuBJo^|z zUR+(7TV0)5U7cQAnO<9-Sz7@;GCn{O9KW z{u}}+hCqV;s;2!_Ao~MMNiN}hO|dzarD7!PjQ$@2Q+gs3|2eq!mgkIR@sMwx%2edO z$i-$C2LjA_Q$+@C0mKTG`7>n}W4W*T9tYRo;lo2&KdsZ<28;iP@Zm~nLUz^3M8H19 zKL{{O^Usv-v-DKfU;>>-jH_b$sY<3%0!Uy{r9~$2wrmWSr|R4tM{> zv5uOG+k&WGqa6Q}@TlT`zv{>=RGs|A_A;O7zWzXY9NAZFpBo^11I5T@S zfT?nOHIS|IYTlD9+QQgBN{((VMDSvJEmRbNem#stT>FLJ9JS1Pgd+FO`ZrZ+dQ%TB zaldp&e&w8vXg$ZBjTi$Te4VI=S8o!9jWTmKS`G_C%d!+AuGRf zcQ+4G+{v()Uq1PEub_G}cdxMiVt22o86d0`x8qpumvqzQ?U(j*@9mcjOEVsnk84^U zR7{)Y9aPRc?j2Mu`!KQ;=})ipNe>AcwnFb$?j6=0bu#{}Kby2HLgU%Y`-yhDUuN6L zJ$I`Mo?!Yt8Kkg*@xPRP5=F4=_O%-Ex(nFV( zf6~iTwSUsb*2Q$%&oyOrI>4)C)%tXZi%JTfZ|U~u@ZF8Vdw((7g0oRYo`bV7RT<`g z#HDoXz?LlmV;)d|;jmr?eR-2>=lk~Ud|Ivb<-(_}f=kGv^X0+ik{cq+)v_0^&DDw@ zZQ<2w5YOS&TCEMV6d0*IP*U8YY)*2>utFv)ZuS|&&31Z>&CO1>$e7m#qFf6L%!e@Q z>@UfH6>y?H-OkJg(I_>bJdLCJ8&m~f8Y_edS-$aDqGsW)eoMW6nSODPXV-Y@kOZA@+{ z>o1b_=14&`mu(}M1%G&HFog?gLFX(VW`ZDbMZlc5HWLyY`^hS%VrH$k!N$513*8xB z3U7(sG9L$M_ow1iO{Y9w+k(hOrYp=vcYqZhXrY)!FxB08^a&$C35!zqwM87c?uLtX ze;UbUcb9PQX0Sozn$F!?eA2E4`>9zB>18XHJ0j=BGFX0b zDc=RxJNKE$0YWpcfVRjj__5N}P)=Rce8xe&QkFPvZd2ZMuMHr(#X)>1VHt)Y6qO9(<$Z1V??&WIztEGbM45R%a6s+ zY6#K^x)CSKipDLTWr?EYs(w&!fxUtW*#D$|=hHi$$$y>`2E7ce2qi^g^Ak$REmyOu zccO68^s5ROED+Gevi|uum-4$&1FV5c{=Q%9UVrUm?d*Bsrzp_TG`4pxGF*U+#nk4B z{B$e9MKSvGS9KxFQ%x}3)|Qrp>of{8y}B2ru><*LJbg2RP8Vg#XayEB3bUi}7v%*J zL>oAP;~r$skUI$>30CF`bF;G-mDK|UHXr-u7H=-9n$ZgFd==(b2>>eYwxu!1WBh%= zUiENr;rlFwgA3q!C>&vZ$;u@dBsN3GWLVF_fbTah%mW zZ|3qUl)XnTW#1Cy=CTP2z1R&=aT$j2s=10`>iJM#*eU;23pk|Mi&k+3`Q=qB-e9p0 zPyY(~m#a3iLOZ^7@(@=&V}e0dA56{uAag|^ixuW8*o(1BFn86-J6IC(v44%^_Nq%1 zy)?{Mah-zjx?5JcG$N*dokrlgM>V80GD~rT;pKI&_F!prRsROdm+LwXL6GD!SX|0Z|c^?+SSS>l%B7XRG!;3tw(k=>PGD{ab-P9zT)1Y$2^x;6aCZXe#= z<4{3?n~|`P@^o6I9r>3xqp^eKnLGnK%3p5AlF=)&Wt4W+5^lz`l`C?!26i>;ZYGLD zD)P;h_H^fNCaW{g&(>SEo^r0?wSdo(L9c?0+15G&qY;f_2KLSJKD4hMm2tW;?;Adi z$Lq3_PVHbfHve)vw+x{#FJlkZzfGN;qhD#w^)#wRE zHD*g&*a)j?MpQm>9i3aenyhNQKKS{DZgv4aw7Q*Ec))W>UdEK$J-Els-_7v{+R49g{V1tp=_LAm> zp$hYoulADv1YE%aBX;Gf#_O%2?XCIATRYZUx7u58+WYmDw*j7y5wDL)x2G}`ysj#8 z=cJJhuG9Ra2OFNRL$+%Dr1_+(EzC9?qbfA$Pc9{|pO?0u&nG{KUo7wmnvo3d-ptB+ z8_kZ#H&WaGPQ!N{!9uLrH(=U7>B>JDFCdjSAYD6PQWKhT*tT~W6`j`K&cYu6Q_gGn zPPzH27(i#n2G&*w)=vjEUIpT7L5sOr_d}sE@cNemTuKYYa*NNMY%tAu!K1&qlor+u zVGcufK}FR;i&w!pRK|;TXhW)w40gfWu_3$FA^Y3DIBX!Rs^CR3*JXf9xh*>^9b#Gq zbMTu>$rp?b1|5GwIWC-_Mv_wV$-0>>5?wgJ zrKFOJU{rlK1NE`CDjeWaQfr_$a6_zhRU^1`zH#q_(ZyJIRY$M@V9FU$HhT1mPpIu~ zfjqy!l%v|v^={Uk)fR%+-vBV>2B(>`8QMiAoP14`-b_@QEDZLwZA7-YhEB8@pU@eC zmGn;3FD|7ob{NW@V>sgHXvfbnwZ>7aN9LViknMGh8-6UUR`{%D%qM_LIYVcRV?CCP z+76EJ0JxNKkvPMVy~}pz;3$89OBqLdeT8~i8T}35Qoi|yy^Ks)72G`?2XHBEILuC# z(X`*7HZ=QZ09;D?@K50ANrTuzfJ-?;t+|7?A&r_9mH==mp+N98qNrU6Q7!o2NAanN zU0hK!c5ZPb)Y;{(vZOylQ&5K zn3CBeX=)kulKb1w>mN}30djW8gj=WqngFme899L5=`-5JBnkvS8Q@a-#=>QSqY-oC zP-;_%W5ax@<0#tST~pZ`$d0nixTbnl3S^Zeoo=x?Fq)Rc-nJeKaHg zBbRdQc}Bk|s#b1>1%ImEZsO!ox)H#o+$DOk8(U)s|Hdf`x;C?SI8&eCkxN<4o(0^G z4Edy+eUOvojGWIMPUw+~;>D1Y?3AF8i_%`4 z73GwZP4GdU4eTTemR!b4m?ivvlar#G7r~dC5ucq?o9Dv=r;3qR&z@JwpC9g@mpPkN zAD>?zgNPiS0|%WCTaq(;lUu|99=~}~zyOvS22b7O!$TLOkmtAa7uvqbug%ThzVVog z2j|@s;F*9K#tR-)3y8^!9)=51!{N?y@mJ*wA+u4NPPse$#R9&F@ZoUC;RQ%5aI;Rx zuo$493}93N@JVeEB1%yqe-X$YxiKD`2`&j6DSF^9mBlaamM^9wbZAB{nRbF;F25}x zKq*65$;VSDVc#nzX9tClmr~7@alI)Gh%Xgqv}1hvw0PzirrjvQVYR8sLupbGlB z!thHKhD;esN)>(|*bb%ajG@96rIetr5^kju#9r-BUiE&i`oN;<6JZUkZ!wX3kxO8O z8DSa0TuDq_C45SmNnlNIOLfT0+HItoh=kgc%wnRJ+Ghd<@s`!|xi!cog-9vYeqyzm zb#-HYwYj%-pC$^?E$jN@F>C^>ABIbwG6GFiwNe7cEkgzTNQn+%$pl0JuQo4`q!MH=|e`y_=hBSn3P(VQY*A!?s;O2djdFn(L@vBBwj$i=outFgh@-*Ovr> zD-*yM1kDPJwe~N4gXK_@C ziu3Ik33)Im9X6;R)b5Bh_5(!(TlQmH2opQyciL^|y99B&APxb|AvMjr+FfGxA)f?0 zotz_`_q#&&9o*)L-pvR4%!l~bcgxLmht~Iy;r8^i_q;(wLZ5H*ynxPSYfR6FN@eOq z1|zNIwZF9LLUx13ckcAu?5&6D^Nj8$Ek?{|Ym90}U{1CpYevjtYphTODTFmnZbEko zHkN@ApV{@)S+!4c!+j}6E`S=aKZ1?z4~g&ZSrr@%rs+mj%=7Ao4r%Ua^XV(ihr-`S zsJiP_(ChKsL|jv7+zITfMIGADgUd=BT0=oJ5VUdw55wGdOkP0mB@RayBTecJhEcWA%0@HHxsFo6uRPt*427MnWwlWG&IEu#;wEnAR=$LKd#z;aG!7 z*#Q(dA&YXro}8funo`dbDYL#dghC!9)!ckp)e!3f^b-w|P1aS3rT7 z7TWSq5pNZTO8OCq?|QkzY~CM?|IF{bMx7(Ok7c`@Fmwqrv^{7uw4@m~1H05(Q{ZVKtoq6P~we>~Nv~8G8)ZN^a zkk#n@ctC+25qP+TdA?b8zVm+3AaGtJre%qF)TLms-foT%Y9vf)0aFjuWYu?5Is0jT zx-D~FFK8L(`|w6xZ+!7$b^hE|Fth|1{tFZ+t9T&0e{p&c+>cBpgJE^DrDuwyn;cz zF{}u(0K+RJFNzD#Ykw`2PTDeHbc9TTtO`}(17z0(iyP_C#@AntGzTxdSe&w2-gF6` zE2!VtDu9X`K=~*%+MjP{vp9O=gCscEn{_aSkhDnndh380xvdW`z`Ty9jOI-)~^~UG(%^+&R___G`|${tL*92o>0Y%?Ft6+n4?9p4f@G-qxpv0G?Xo~%~| zIUcRGD38Dt96J_|m)_0Q7emHE0SXEm@{h)rl%!)vFv#p~7Eb@@<6uF%_5wcc` z%Rqk<+6;#N>`%hsxZ=ZR8_*Yn5xnm<=JcB8D`NXOJO^gzX?h!2~C-^zK8cFO54j z4OEaJ3XAT-{y49#K7ZA8g?w{9#k=G8BOw5wyVxd$*anA@UZ?7w(!RV-T1tu04~{Fk zPUpM+;d)bue>2W|lP(gM8G2LP6qhR!oWFF_(R+POcw4&^U&b0-VSDR-eN!iLXF78` za&_DM5Ywu1*Z=cdC*;T7`?|XUk$b}HyWypZQI-4I(6C9@d*OupIa0{d^!=hr=`t&1 zn>BDsQ1XF zeAYceDH~4J0bGQmePqxIT!bT?sC{hI8B8qVL!@(J(i_FBUnn=pq+4Ni>;gd`);+Tr z%}~M}zhz&S?Js!M4Zy5lP{@0#QE=B%2od_nyjMGh*ZVR zp3{4QBJj8-#bSu0UToqZ+Vo=a*}J`dQ8b?Q;c7(9-wD zmX}q@sL-TQ4}PZg>f=U*mcDD0jpj>V>PiVccPVZi(-_lA9rHBIO6@mUFMdv&7jO;w$rryUT_~7{uTNzcazx?dDY;;`NsM4=qAK)=? zN2IAX@T8ra()Y&AL|XPF_2D)0r_HR+_QLPvHTEVd{Kci@Gx1=ZtTy?^hr?$YrDaxQ zYAZ$jMDCk;rVd%;t4==i?;inKFv1doK(Ltbg{IabEmT^!7(^+|&wU$Lq2z8}-Wi+?r1#Qv-))iA`3ArVGJ}*bh<}&MS>V9?#@Wws_*8P05 zH7H=$f=KJUTXiper__vMUT@z;dUbQ{n&?d zqW2TQ6?Z*j`o2PrGg?YtKg^+EeRW*;xYcmUVA&*e_4Xz1*vGX{8R5_5?zoNDCk|S| zPCHpyF2bANyIk~E^R^nl{A}|5d9&Zeisp1QBqQ?mocQSv=gYMPm#^1bSxu0RSyz#V z+Wx90SAdANta6kk0s~=mftECYfnZmQCZQI)J?Te!z~dD|);w}X_cA~rbQDE#9QGtl zBt>|}+v+O|_M(+EM1DpKFe>egi8KsRO?f58N`}38B+*dcR7;RtsM>M28-kU#B`9!4 z{8(U(z_z^Yl-wi!Dqh-{QPbiy;v)fC?Wwityd4;t?1B1l#&|ImQY>b}fku+X1QXp1 z9MQ+_=6c3N8@yeT6&U0$pCpOiE`ueGJaD7^T3*7ebSZW>2WNUv5@XlMO5eP(P3IUu z)L7}_IT{Id!=fTb`z$L@T^jxa&XmsIM8*@!1KY*RlllQqwhA=@*L&TB_5+=~?&N5+ zF@>oZcNo%Z3lBs%5HuDM#9hwg%swj4d4t9npI&8gG;WAV4_toLjmO><+fLN|OlJm1 ztJNW{KarZNgKuzm!c(Wl%8wf*bx`x|Ny74cUq&~diqmA7__hg>K-uRW>$m9%z0S1B z@o!W-aK@9X>=8I)?E&4V6+1PoRZqCsSN+5Y$I{N7LX{7U&ea-fr79|O&pR5vD@ryC zCq}8;5~!=uiU@~6&nhYpRzINtZUOBU5dwq<8*wyNr0kd=QPA`1RNPKp1kOZz)kiMn zTpR)b(NxtxYQ@3`Mj@k-Q+wTPh(U-gt0+bQ+;N^#BUfD z0Fm#o1wH4;*z~}_mne~|JS$(4YWyt-&kCoIOK3yy-ENn*TV-hs@eUzT@f0<2%G0cE zDfU?CU`rF&l1DBjtNNpoMKHwc!>acjzS;l^!zCY{&@m4XyejgDzV0b1ed#w+t1KY- zlxNieAR|{bpbJfM9N|Y_&DO?LJ>wUr)5FkS4QNa3edJPF#g_U}5)Si9vt^>-KADfg zwV6J2uF9PW^Qo#|U!XNdgz<~0)vSjJ&-=21Vi<38-gfyN12cdi$XJn*@I0)`%~a4B zLSSp^MpT*e6+U(3r_)vIG?6Whg(&!m6?WF1w{^&luKp{RiY~g$5mC!uU`i@jA}>-_ z=;32Eyq-1qQkXM*HOgDwp3<*H0w@j(v=TbfYLyO&8>q|BAvJx=6Kt`!^ky!t<^9-$ zoPKk4>m~&4u~?4pzP0k1KXNH)bKdz9__8r>A_f zTnAAcX}=d2)&=`dyv2(?y$kL`A0-rUQ~#2m^bH`ziS|!EC)~=_k=V3p*TGE2Kmvqq^NI8C*Q4qJp9$!@ES?f-#pB z$9TFzKC#2zk^GJYg=ey6lo^ygcG_PX7D|rWBrw z9&(Q76SDybGs4y7v#dT}Mnc>>l z-h)~8#U*P+*(KY!$}u^ZBy<=WSDyo5%C=`Ft=xLwSrZ?@lzRpe0$#LLo^3(^n9?d- z_)J2qMM7MoU8+8eUr@4FPEtCuU9KEe!da5fUs8TtvXRombz73Oq+P-58!(8gdDSW3 zNyapLVuGZe-(donN^KK94J#0>XgGkWWO0|$S(kE1>xkC&c-qpTm!HV9-(fsoN96}H z6$A;;fy|jazF&33*mYvyOB-oKyprs+HpzW^=Kp3r+_GJoa75aMOGekIGy14A2}&jk z5619)yFFEcgJ74FM%qV3e_2(3O*(&rJKwjEGnqGMlF@A57G7Ng%Y>gLi2!n?PkZW% zJDEgnnIwxY`XCu^ta{G(-N8gvZxdw`vt=WzJw1t{-nMp^8p?)f$Vs2bIugmobi+Qy z==x?=N2Jq}5L6}?)@@!6`kL<#U^fHPd*bEf61n6Fkh{(1Wg;!);*R8;xj--P5QW@8 z+2vtzM7@z?-D&IcnG7E4vLH_lnWRKe$(?WPI*2+9oZm0+&e>V$)wf>S8|&8XSFVu6 z1|JL6n}^j~$rP6w)Yoj4nwkH!@=O65TCqh#QJt&5jVP%~58zTNW>_e8!F?-8?C&4% zkIh!>;|gt`?;nnA8$46ogi;!jRD!Tm4UB4pj42LGwYN@~C>^*fPL~hNF@a_hmDcQ( z=FbL*074~K@Z#OTszwVysC<&6v}Q8cEjhT3g|=!nxD(mD<)wV3qr5vlm{vZx*N(C? zKX^3Wbac>sdB|w469HKb7vIOT#37*2x&3oAs=b;*2mD}~9y|$sdpuqe6 zA!w<_2e{!Xuqw=p;YrS6SnEKzSHnp6^#~J4PnI*DKUGGG8b)IlL~ii=0$aeXpp3RL zj2R{HBm-#?WyIH08S~i)t~Eba6jCuIJ?erIuJ;HLGcSHHQn9CJRq4n~`*UJyHRHAC ze#=lY$%1c#Mt=Zo`h9(kHLdQJzAPjIrm}4Uz|s3Yu~up70ckm$|&;iE#29pya2Y&J_=!=asFk z-OD`?XPqwdVv4E^ZLLWnL=vzbp`9303T}qJ5eSU;q?`7X5iK(vgX0SGkinxrHmbn( z1{w>vXB&kgUo-+EVCn6O`zbYwzrjW1j?oOLDs7A#VmH4C6uO0hmQ&IYSE@Hh;SB@~ z^YDHsYA0#()r{uY zNLdy1*YZ4k1{q^Qe&fso1=b6j*cq~Ua9rRQF9tXhH@xNXm}PmX7nr%cz&W~5f}+GZ z-h|AI)HLg8T4CSk8$K*vm^R3I3h|O@z&E=|bn6_R(|8r@hcU9pR8+b)%&MyEPnhao zJ~QYhBm>)dsb4j)K10e;&r=KYB=ut(+IW>9<>$XT%JLP)4&I=iczNU10xZo6F|{sY z0rn>I3O0HgrIew+sv%DF3Zugcu8$vH(h8AHDnX^8Z?hpWf)VNJ3W=E?Df%j9QVKcE zDlUTol_O+TU44~W8i~?nmEj_pE_$^d&yaC*)j?o|Nji%8aFyMKfeB&FJ=usubM1kB zjZ@Q&>-8FM<_~URW8UaBfyyUeVnw~YsCbkO**n)n9clOxj0JGkB{Gw|bhCY&=<-k2 zbn$aj`E%2C86+!B+5&QO0$B1K)@6jF8gKHN30PWd*VSwoI*gdQcHz5E)^(4WmoZsZ zIat&)H$043wmevNQVTVOqwZ#ljJJy(ayJZw}MZ?VBwUm#t5g zTjh{AbHc){FPr8?JzKboTP`>jZ7G{?S)`MRx4j%|Jh->5>dm};wjtN%Sm@h+l@{OQ zED#H}12-+Ad$wU0wnK1s!mcf#(00P5cLL~l?gV!t9e3!AwtZ;2d|h_pxs#$?hN3HX zz7OIivZ#FD+)2*F{c)k3jI*02jhou29xuI{xk!{TI8NiZn}gF38@)qk{5E&;?Yqj| zJeRWk#oc1Ft-_1l_(<9kY0E|TU3;nRa!1P)_b*+mV-5W=L35U z2v&A9d!>uuH1z$p$;d{UDk&r+t-*{geAcjZYzi zj*3GF2cw!#M}!qexeq2-o{nEAOqd-^H)2lNC`?Bm%td3)4$9AU9xRezE|ADCUL33- zbhOayXTVynY94-owu#3&-0(TXdUN>A`EYx3Kb^@&n7e1qrDMJF@TW~sLpplSd&0HN zL+BivBZ!$TmqLz(ZQND&bgV*ltWA)j?fIbDK6lU+iNvY!PpBH(yFs&DNmPhq@MWg$ z1LWr!k{xuYg^Qqnc)tI8CWSb%qgc12v~Ii9YP;BJyC@{NSSW8^PofW^14E!O4ID7F zVHkL!aemtL_#87_`hOVznPUP%`U&@~`v`pqMD?>P^5{4M>fJZqckx#a3CVU*vZqmO z4&R{qVr08`wfy-+{8If5u^Q(fFuh$b^NXP)HV6c)vcRD#Ggk?KK|4D zB#RHxV22pl5AmP6UdZ_CvHJ54eJ~L5mjOq~f!#4JU@lP3hwYEmv}|>F6v`rxB2_(} zeC$ed`w(+<9?$kZKKUYg`{IXlPdt*hvC0Ll!G)PhiMj8^&%u+ZZ98@d52)P4-_{l6 zm;Ny`zc;%1W8&3E6cPWgte2#$S8==_%S10QRX;lFV~V+6KC^irA9fy3cJcj6mZIxJ zZuM2nr_-0AAfqS8F|wa*wXb|W2I@Az>2#FrJ6vtrUHZjbCw)5mRCMNwbQPcdfs+>b zNBOD$7AQgFQ)KLE_(%T;k?RjtejM1WPEr}yUq8bVoX2H3#UAyr$oO-{oaws$ED8d_ zbbU;=diQPkDoF-Za@MI92FedTkFNe8sPct?^d?sH=DpTU0Mvrx!aP)nQ?T1rZqaJU z$1`>fIC%Xu9dzUIo-A^SQcPkI=5_;lMe^+-jHy z!l-kX)l8PP18ghnojt_$oo#I?riF=4)5g8$`cHE+v!25m~DjGA@VJ_7Qoz zA119@zQr*`XAl98!~XU$Wp@~bOa#-L6RO@w2CaX{r7TUXcUsJ(r_9_7@c7N8e7{5X zx}If3n+iK5FiW?};Q+FG&Ny9c)D^*OdBHSW{&p%tN=+V@-E^|PhjHtaWwGAr^5kIe zl6AS+1Mw+~)fL-ndjKw{&Hk0WLbA9&fmE;N4&n>789J!;04?Uh7@2Q>a4CI1plGqY zstYq8E404lKAf-pczSqn%X75c>id)x^85sVSjyQJ_x53;!sB7e4VZX7yV#$tdiV42 zRo)(O;Wz=SEky9?&@S~Ov^9v@ZVkyeeKoh)HKsF&4?sdn;0eapwF$Q2xNB5SpHOM%}W{l zZ1xjn2!wC9nle=ACR^J#s)qL$m@-|5t@DENn-c#uRpfJdKI*6!y8hZ>*W@+SF|VBb zlOs@H^U=ha#($=Xf0J87ldx%ey%J;J%SZd&(wp8m;qz_{?T@cF*R;v*h1X`^y&t)h z3JdhVxRiA1;qv_S8Q$>J0D+n4X%OQJsvG+36as%Qxh;jnvHk`v&x0sy28q(q+6Ey<6Z&QhI5!0mb&X*!rZ!xn_JdqXcR#mIL-m|PLbrFE!`>4?oFh^{GT#l$F*b0GDIwA**GmuzJ)5u~Kj50w zv21uYMJo2HXdGx>E48#fe&wEhlh@!lPQ8XmJ7eE>P|%68+QYs0d9Q(IslrEyXW2Mm zfd}AHy6~>5Lx_3Tq@N`5u7@iK^KG0lFY<13i@fIB`u+vNw;lQ_iEjtjmV|#dC$Euz zk0bas|Nd>#BL6|foQuFAS_6r|&)vZ!fuj}_5y6eROBcbirl(DUrvm~-kc#7Og`&KZ zp_i^g7c-tj_h$>9x(ydAfg-}!KOKGw-S~Lu3*R2pJv0_=Wr;K;?zI76%Im$Mth=+@ zp^WSn#Xf72Tr#95AH>ij zioIwA$qi}nTCveByy%?CQH|*&ram8;Fx4Bz=<&7@pA`EFxBDU6(@9QlANfiPQecMS zwUcukTYLz7j+sW+L9cd9reH;ZlW;B>@}ea0^_>xFQEUghu63Xh3?;!>b|*qWc#sW| z0>%_w7vE_32Ww|al0LJ}x`~odmpkL5!`Ln{Vd@Y!K`M%;{@h}ikkW8@_H+tL`X0HP zpzshYDk}P%9wmoT<3R%i&XDIpudBte)T*f%6pZ2IBXfUCj3YfdqovWp34p|iA^#{b zW+_s$O&P%{$9mv4x>0jw8umSMDM|V|pXEcNnFXC-sUy*Fk2rWKC#K?d7$9&JhNGEB z09?v5Eur?6U`xR@yvA(}?lrk+O0QPzF4QdEUHidDF6DBqMkY7B%4f_o#&izsT#XI} zSB~-&^!q4kl-_nl;qp|R2D(TJhUesJSiiWG=IJBBi^XZ=LiFM|;Jc23Th8H+se|?S3s5_QB=@C|uzaX%uyT+iEUpY}Q>ba+PnyZyMIZ@cv zw)YyAaXRv1qNpBr-{5K9bO0cq7EA0KQ8Mbh;8MOWuV9&lA~j*udu`H+SoH$q?Pq{X zc{5zayxQ#gHpO3~T)YyH!|hulUv2_i%F6dFfyoK}q)4*RB&uFk&m$M$tf%S@zZOxo z)KQ?Pw^S+|yHTdE`kWKc)j*E!S>532^eGLY8tUC(U1x3y=n$#_TBk7~kZYTkH^p(N z9#o@JT+!7q0&OVCljTyvil%#1Q$`n9M2q|F4vW&`Z?{qA3e3&2z}Kpcw0yR$$A+ck zZDF?h>FLR2LKT1ke!#F{dS8r1`9zDs7B3t|Tv3nyj`k_XvM6QYi6K0fOrPAbXmo-V z2$iw9v)%xf(j;#^%iuGoFyz>yhIgHBOvgv-tl8C!-Is2E1w&3Vz&t$_#3j3eL8loI znZ@{$klXQoxHyGUb|je=7_yxP*S*9CHC_8PioFjM#(p(u7+g%Ju4 z&*s!!#>*X3NeY1sAPEw=6t6!NJBh>;SHM_U6N|cL;#hKXvT}`h;#g4+mykTmS#y@w z0Kk+88WkF3_gJDP0;Bq!XYm<#i3Dukux}8;)M}kKZ55;%MpMvh`|f^dSI^PAW^=@) zG!Q@N)-6Gl=do*;u@$y!>5p6_YTB3x?570i8xb7+B$@3Lp%NrvLqpOnxB+T zc-fyX5W76otAQ$(cQ=&@5pB_t;qB z#oWSi#SeA}hPeT=FWqp%;Rn|0Nc;mVdVSoX_H#$_T;t0Y335iW%Fk-0hbD9}Uzg>$MW;uQsG2GV<*u%VHUQKOz@Mq+NF!`>sa-T*|60wdy!C zk)Xv;8t@R-pol4df;&&1MEPMsViJ9;dE2gzN_$sViLzAZ45wqcY z_ThXA8Ac<&)iqd^Y)q zj>V~`-HAn8%|9(ol;}iHz)V5UhFilx&x%>S!tFmMP|qf0P4qypNk-`WaTn*axAFri zB)JB%tv_T4e@+ZDL7wby?Tsw&oy`vat%vBnhQV};UhMPli(ko*I7mT&Ds-(&Ol(%*6Se(8a`g8bel(*i^LcU+0bs#oRVraHLt=GGqnIAu&M?WEZ<7EH_ zxRmI9ZsCTFv4#YXT*_X8(v{k3Ln44nDc(yoYFIUGNCI#vFM3E$Rw}Oy$p9{8sz26v zn~5`+Tr-TECY4HgHIl7uUxH&8MZ14|T555UM+?oiR`21W!PkKx3Luqy*bl>QmlT zey1nwtpJ$P77Hc~Uz_i_IK_v+kK`-?{j{6VLk4n)F_ItDAW1gCngV0I1|Av2&vR+(`Aw$J|lF{Bxms z&v#lUmZeXnS^ND<`MOBYIm6FmC}schW02*t^HeZL(%niN(^9z>AyjfFS@VJBkJ z@SAW>6y#+j!%pOla@6ab7`2ztHaQ`VJ2BDp-@S8U(dpyj=@W$U;xp;%z;6-;b5t7W z6CK_Ji~>-~1QR$39H^Y=#hudbog^Fv!=5&FXXpj$oMg2(Ndye(gE}}CEm#&}!5nYX zE>_~D8L~nqu}s`ah}tsb-((&lr#Ld?a%%)ezg16so7=c6T)>dW(3{zsTYwy$?*fm+ z)jD@R?NPg1fUx(`aJ%+O>?w)14-9aUi-nPPgF)-+)s>V-5oUHNK&9-&EpZHt{O%X1 zxmV$`N6xpWIZObblwIY)SUVYB{T9Bal+-WDvbHmipkPnE+_Gviw|>)7<*OQ9uo}JC z|6=bufSOR#u9MJ054|f@LI>$R^xjcWkS0h+Pz0oCLhrqIq*syNlp-iaiU*u|@dw0Lx{pXw6XEKfjW*o=!)bpOhWi@qja)|wtt9^tD$Nm27cppoO zrKK}Hj1fL1Xp~1pXTB*rOX|x*feoet*G&aq3Jep{3%^X2-=gkoi0B*5uez{!?T|f7 z0LLf$2i|N}+1L9=`wNzd8A#fmcvE>?rZ5v@qmkh=8>X+fR!NgEF%z+*aothD*IfEe zUIYtRyuF|@qw9GGbE}ZxI;X zy?fFT_hgua9#FgU}0#5y_LyA#v zbdfzUk?qW6cH$zIU`8UllT)We2#yzdjH@#17v0qLG~Hx0v-2_?KHIeHzWw;D>?4!= zCt;Y6w#m>EeZJ|E4UIrPk*&tp=bN-o+$m3AV=cNzXVP#)VdJ5LCFHTsjNxXz>j0B_ zfB6VKiQF|)l-RdeZ@Sy>Sf$U23#Dw5ujksM4u4z}?8xgNXZYTB7213a7S+g-B+4a! zl<(d&wQL@@^ZX%3jP?=Hk7P!-j zLeJ)Gzh>F1=bsjT$gB~Uzuo+}<3svDv%-_+2U8zXABZb$i_fh!C%H? zv76n3Jlmq%+ky^}(2hgM-fS@_rO*>;(t9Fd)NW*G=5DavVr(>Fv|Xu9DQVh1emwP+ z{^d~|ek0kNBs z@qVszQcu~VBxSc!L{H|m}kR71{vNt_S3p@`?k7kg`cbf5CXgkZIOPf9_rl{tl zEOE|un=?aA&Ph$4N=-O>0IuL)VhIt;*b-AvlZsZ8wUqX2m;N~HSm7bXE7Kk@7DJ7e za#B+ZyxJbL?e8}hCRfrX){};eMum*Ukm!f{vV^(RN(b#qhYOI4$D(NSV?|a%x%Z+W zA%}M|%~9Ox+hSDh(la;1v^rw&$cf835ZMDrgHU*JU*!3Y6nv-vvHv3*HKmN5n718w zPy-S>FxnYMs+~{~<4|}$nq*ieHM)-*ktQ4-P!k>I^Fb!#ZM?pcil%0$So#WaJQ{(# zm=cfX_EzPCw~Hb1E+p-!9+S=6z7sfy$Gzh~%c_QC?L_Dg&zE9K!qOQ)>ch@D;sLrN25K!jUM1rkPVAU%YD6564f=x-IXtY zlW+J6RW=N19LgP!E;Re%XO7nVB8$up^}Zb;uKu|Oyj<80yjj;CmxkV>N2v)!qlg(X z0@Y4^tKZp)>h>$|&VGeNM$?p|IjH(E7%}-A`Y<7lJ73gV!n2aEBmVpbx~M zH0p}eyw|DLpobJN_md!uGyOA_DC#B5A-Kx4MF`uJBMBU{;QZ}g*!B6eZ?jxVBnthr zw?jzoggkl@!ls}y9S5OX!qCeo&RTt2DE$VP*qc?mK^5H3pbe$TzCo-FeNe0Tu>0Es zt#63NORv5wZ4&ivQYyb==zVus`91IDch7wAD|$x5RQWst+ip&aF9qKTk{@^Oy-o`i zheu2Z^jCa@iO^!E9zuDI2e{QS%~~qlGyQRKC^q%buOd*AY!v8wm40nwW)fMIy}S77VOHA4v|@MFGqp)g;5sh6 za19Lpz6g~mr$!XLdn~u>Q4ZB~e#$c(F3lxyQ6;7a;Q@__a9YDa%?!B=w_~*y*Urp# zWxPJ7W6NWZs~$sBWm;kV?iJlSW3%)9ri-<_SFJlQ`@efW+nsgRw)ZlWh+ZMv+x})0 z74Pw$Y@gG&lQ^_8fyu1XSeBUc{hplj&XYwdQS^$rzOMHwO)8IH&%NM2ch&xOrefat z>*wCT+M-r*AZ|W=E0~;?quLy4Y$~N$LR;;WtesvguA%}X^VPLeJ&HLrqD@T8dBu0c ztDxtCj;^`~H;3bx}pFIk0IlgIzt_y*%>nvgVwuhYE-L&X8GaM z3=Z)okxVX2sd2*HE9=_(iaLVz#zu?SP+I&dRk9ayf>tVSYJIY{K$cg6x}l5!a!2&vRXiuYGEFh$g-@ zX=CZNu$pC`c;b1EYeQ$2(EOu;=LHwvEW9Yf6fV&5=o_nM6{i@Ud|963#eBv#l%s+% zoUZUW^Vz}9REwm}HxFK4?WeF>zcwQLaJ~7iA&W@z2J2@g_YB$R(x+opaPT&sz~);_RcXIs2qjpB943Y1)&UnV53hb6%dx=~$((*?Afx@_6T2ik1x} zwe}R(=NBbOHeX&=Uw!=L^|eWx-HlG(z3s;|mk61suNA)beQ?*v_S@FN*(cw&pC;Mv z$#`C^JhXVV^A>Ze|K;bN&Jacvlp+~TAh86c%0>|gCu7Kcm+&|kRfr9fL+M(U2-wz? zzpI=MzCUdHa-BEn!+26SkHk}AlWbMGhU5sLMayq*7>>%X3SYk8_>|lyTaE2aa+Ko7 zr<52*bqEzm8b7BmZywbvJ)wZ7h0aNz01~8e3OzC^6}YWsOv5M*yMOCnr9qT zIocY+z!B1Sh3k>IHdXA@rHqyp90}NNjw)z$t`1(AU_8ruKw%4^$!|~{tNW2va+&(v3wq|lrJbKDk=hw%2*fq3Lq)@lZ8CaMILJ; z|C{E}PdY>3r$B({mjL?J*uYxOH#PmC>-@e$AW*;u-hjXu5GYpz+Z|v42weVvJQmR4 z0ctuxCg;zn>HLSkW?zrz(f>k7*4Po9Cqub>Cnm*iqyU9E*CO+wLN8e~VOsrxX|+y; z0p1-!{$u5JwU*6pkN8PRXdCRVX)+$@@la(7sqO3WG`l^yLaxV9JJ)4h`Q?1g9QTtO zQnj!>z5wsx-bgV{3S!CC<-~+<^z?k*ubvfVq!~u)OT3z%NR&SP>7q>4%e!T=gtL4z zrFRB-`37vNs6O4kgFagsS-rb@_pu<;l=GJajr@d{ReI4EX&v*v9cn5p> zqJ4uR12J(SVTs|f>51uunR#XTB{fB5^(7ToORJlz>RaoZI-A>Xbltewdvkbb_|Bb) z>AUylX6GI(Jbb+P`03Iz@OfN2aGZSwjI@E3EHKjscG?>oZ{Hkfhhp`cup&-C$LT=I z3H(pmkKZdg0d=QeM4hnGPQO!k+V^Dx@v~;rAO8HG_;2|AFS2RBeD7cTeeh*}?T`N2 z@BfW&`M>^0|McJgZ+|2B)1SWp{KNjHz=eQ7_Ps5v_obtw_8s^BSkW$$(5S|Gq^W%0 zSba2Kr@3N0gAW+1k2F_K7NLLw*DEnztaS9g0#_Q4zYEcAt(o0NJR1|#X{H{lz~x~? zVKe?ifr~_~ECOr)84{bFhgIORSQOZ*UD~(*^wYC^Iy1Cy|C!(7GjKb7-$M9exl?$y z{Jw?o$D5M`F~8-Pyksi(!P6eOKByR;N>wfh?)mB|sp?!;zD~Sp70* zFa(A7FeCX9?e4eArO?@!rBD^VuWC>T*Vi=mFvc@$4{d19ZL&jzEr$yuxig0!*c^d0 zLg256eM@3QZ)q>s$RZ#pUu1{Om3W0u*^Kt9Y*nR6>hw9;*5B6&(dX5efdW@R$uY)s z^WzK0EzC{=1unaU9ZsOY^>QZ5A=9$vH%RZKU ztvH;XA6Xo|95Z$$Uh0)gX^bXGr2tleOSh&vC)@88UwK|+*J^25CP{Twm7MFVn(~TQ zZb^G}x4x{^wqzn-*H_-Jd40L#Q`cHU-@uF4js5g2j~j=XYuC>WOLea|_eu%~waj!K z*=U_T6ExrY7X_|29V?mc4IK|^eBX5aMS)AC{bj7g+v__(f$PTCS5{}Wz7Yyy6}YZ` zckun8z%_V8p9-tMg|Ej-g;n5Maa%`V6}akS>ktPDTtoa^AKwE7E(Py9u!Xu06AHa7 z=O*PLC{sdsY@S)P9V zjz#sK9_+Y{eR}A9GtYC;caH5`tKW-y>Akj@H$Z`_;}bbh;F9MLcp8m&DPTGN#r1$^ z$v3|RtfUtyUR=!%3%U3_-}d^&7sc}5F0Pd+lY73bin|o}sxEKO<8>pcV&Hm9vqI2D zM_O>uo1XJML2vuaz6Na$iYo-a8+Psqe1AtRIQYX<`}N?DGXmcOx8_N|27g-2>JHgn z3fm3YS@{qW@_Fs5{G~7JV_lbaH!FiLef^l%>+x;JNIqomYd3gJ21$SN4W^BP^4Fr_ z5{uud8T+=y5W#>LvCWB8SvPrY#(1ZgPwyYt(0r=(ehl(#Fe)s`Moe+EgY=RvTp)rM3i68Gs+KD zS{(1MC24%+VoyO1s|a69j`Zc^q+uG;IGP%p7`#Hq#it`=+LU^#hx)rdjjwX{MB zE~Hw{EtAyLgbIaKmKT+yGp*M$tb%w16|+W9$TeiP)#VAEXKFq5zRC5*SEuPvKK(iJ z#_UW-o};qWcbqOYNR*x!bv32QxZm@fKdIWl^;~n_?AIeQ*Z3xUygs0xlkv*k z)0w!?^dbLNptJllKBEBo+QLtJWb{hkHSdJv))no|unFVI-N8`LWWQ0US|O`3UJz?3 zLE$0Q?LSS$ywSfxPj{##xbJS_fdW^xwwCU6YA#lR%VKmov+HBIXhOCAz5>^1`bn+f zYD53gdxc-H3S3dTGgp|msoNXxAn%W&_i0aE;g6Y>&>}__B2sOU)Gz%VvHXpZ`oXE)_bsV zZoB32w+1&F{>O#(?LANAL;}0ZA8$QK>UmRtBIsFO-R88k?T71UufDH+V)d@Q_u3Z` zv<1%+lqI>4(C-9>rVjP;Xh3Ie$mVT&L3?XsOYo@3JCdpADZc-F-{C{v(NKluy-dqq z-Wz9QUv@9=w9Q`^u{j%mSN_@i`<6Y@dtQm1-OtuP&37pmdL@_0uRNze(W#~6l^WB% zvLv<8ZuHnQ-DlTw{N!I0xSRxyI@@eBJ+p1)&)m+pn!an5`U#4&={wBK?LeE?j)!=+(wR6p1Dz_lX(79|}_NC8p=KQQT zXDV7dU%LC{<`yNMsVb0t<&?>sQz?9=CPGIjuj_v*aNT-o?mzz8X7|fnA4OYZl!1qX z1zp3iA$Z6K6u9Jf7p4;ZT2>#-yIJi%n4f5CYaie63VQhP*qf$~+XipWdCx!SsM+g#iLM^73Qq_03=q9uyd8eO7 zhVQWRP-w?)-(3zI++mY6)JnX4_n8;|XXZVPM9tK!yDJXrpBau#XkE~gd`XMgZ5>YZEf;5F zFjR&RIUw-pqKjd0bQfU-LV#Yz4vMVm4hxI)zpfYe_RMzB1Ii_+R`n8;2!(<>tdfwk zLh#I^Wm`gv13-B?BV;~CO{y)Ata4UFnCoTmG`yX$9QZzak^g~ zP$ok)ucPpcY|#YgCXp6Xk3Ug)!`L9cPFnoV+y0oji;3r8W4utSC{m>*DCJU+S345f z2qABT)ocs07>AftLb;YO#zat&Mx+=d@Ph@E(uJS9G1$El-2j0BWiFvc

iJh3IAG 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 literal 0 HcmV?d00001 diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000000..321618b323 --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,61 @@ +--- +classification: +status: draft +owner: Mihai Criveti +--- + +# MCP Gateway + +A flexible FastAPI-based gateway and router for **Model Context Protocol (MCP)** with support for virtual servers. It acts as a unified interface for tools, resources, prompts, virtual servers, and federated gateways — all accessible via rich multi-transport APIs and an interactive web-based Admin UI. + +![MCP Gateway](images/mcpgateway.gif) +--- + +## What it Does + +- 🚪 Acts as a **gateway layer** in front of MCP servers or APIs +- 🔗 Connects and federates multiple MCP backends (auto-discovery, failover, merging) +- 🔄 Adapts any REST API into an MCP-compliant tool or server +- 🛠️ Centralizes registration and management of tools, prompts, and resources +- 📡 Exposes all endpoints over HTTP/JSON-RPC, WebSocket, Server-Sent Events (SSE), and stdio + +--- + +## Key Features + +- **Multi-Transport Support**: HTTP, WebSocket, SSE, and stdio support with auto-negotiation +- **Federation & Health Checks**: Auto-discovery, syncing, and monitoring of peer gateways +- **Admin UI**: Visual management of servers, tools, prompts, and resources (HTMX + Tailwind) +- **Tool Wrapping**: Expose REST, CLI, or local functions as JSON-RPC tools +- **Security**: JWT and Basic Auth, rate limits, SSL validation +- **Caching & Observability**: In-memory or Redis/database-backed LRU+TTL caching, structured logs + +--- + +## Audience + +MCP Gateway is designed for: + +- **AI Platform Teams** that want to securely expose a variety of tools and models behind a consistent protocol +- **DevOps Engineers** looking for self-hostable control planes +- **Open-source contributors** building agents, clients, and adapters against MCP +- **Cloud Architects** deploying on Kubernetes, IBM Code Engine, AWS, or Azure + +--- + +## Get Started + +Check out the [Quick Start](overview/index.md) for installation and usage, or go straight to: + +- [Features Overview](overview/features.md) +- [Admin UI Walkthrough](overview/ui.md) +- [Deployment Options](deployment/index.md) +- [Using the `mcpgateway-wrapper`](using/mcpgateway-wrapper.md) + +!!! note "The latest version can always be found here: https://pages.github.com/ibm/mcp-context-forge/" + + + +## Authors and Contributors + +- Mihai Criveti - IBM Distinguished Engineer, Agentic AI diff --git a/docs/docs/manage/.pages b/docs/docs/manage/.pages new file mode 100644 index 0000000000..958b23158c --- /dev/null +++ b/docs/docs/manage/.pages @@ -0,0 +1,7 @@ +title: Management +nav: + - index.md + - backup.md + - logging.md + - upgrade.md + - tuning.md diff --git a/docs/docs/manage/backup.md b/docs/docs/manage/backup.md new file mode 100644 index 0000000000..2676daf2d4 --- /dev/null +++ b/docs/docs/manage/backup.md @@ -0,0 +1,69 @@ +# Backups + +MCP Gateway stores its runtime state in a SQL database and optionally in Redis (for sessions and caching). This guide explains how to persist and restore that state safely. + +--- + +## 📦 What Needs to Be Backed Up + +| Component | What It Contains | +|----------|------------------| +| Database (`mcp.db` or PostgreSQL) | All tools, prompts, resources, servers, metrics | +| `.env` file | Environment variables and secrets (e.g. JWT secret, DB URL) | +| Volume-mounted uploads (if any) | User-uploaded data or TLS certs | +| Redis (optional) | Session tokens, cached resources (only if using `CACHE_TYPE=redis`) | + +--- + +## 💾 Backup Strategies + +### For SQLite (default) + +```bash +cp mcp.db backups/mcp-$(date +%F).db +``` + +### For PostgreSQL + +```bash +pg_dump -U youruser -h yourhost -F c -f backups/mcp-$(date +%F).pgdump +``` + +You can also automate this via `cron` or a container sidecar. + +--- + +## 🔁 Restore Instructions + +### SQLite + +```bash +cp backups/mcp-2024-05-10.db mcp.db +``` + +Restart the gateway afterward. + +### PostgreSQL + +```bash +pg_restore -U youruser -d mcp -h yourhost backups/mcp-2024-05-10.pgdump +``` + +--- + +## 🗃 Storing Secrets + +Use a secrets manager (e.g., AWS Secrets Manager, Azure Key Vault, or Kubernetes Secrets) to manage `.env` contents securely in production. + +--- + +## 🧪 Verify Your Backup + +Run smoke tests: + +```bash +curl http://localhost:4444/tools +curl http://localhost:4444/prompts +``` + +You should see previously registered tools and templates. diff --git a/docs/docs/manage/index.md b/docs/docs/manage/index.md new file mode 100644 index 0000000000..2f4b13ea24 --- /dev/null +++ b/docs/docs/manage/index.md @@ -0,0 +1,53 @@ +# Management Overview + +This section provides operational guidance for running and maintaining a production instance of MCP Gateway. + +Whether you're self-hosting, running in the cloud, or deploying to Kubernetes, this section helps you monitor, back up, and maintain the system. + +--- + +## 🧭 What’s Covered + +| Page | Description | +|------|-------------| +| [Backups](backup.md) | How to persist and restore your database, configs, and resource state | +| [Logging](logging.md) | Configure structured logging, log destinations, and log rotation | + +--- + +## 🔐 Runtime Config via `.env` + +Most operational settings (logging level, database pool size, auth mode) are controlled through `.env` or environment variables. + +Update the file and restart the container or process to apply changes. + +--- + +## 🧪 Health & Readiness + +Expose the `/health` endpoint for use with: + +- Cloud load balancer health checks +- Kubernetes probes +- CI/CD smoke tests + +Sample check: + +```bash +curl http://localhost:4444/health +``` + +Expected response: + +```json +{ "status": "healthy"} +``` + +--- + +## 🔁 Service Restart Commands + +Depending on your environment: + +* `docker restart mcpgateway` +* `kubectl rollout restart deployment/mcpgateway` diff --git a/docs/docs/manage/logging.md b/docs/docs/manage/logging.md new file mode 100644 index 0000000000..0f81ae8fce --- /dev/null +++ b/docs/docs/manage/logging.md @@ -0,0 +1,75 @@ +# Logging + +MCP Gateway emits structured logs that can be viewed locally or forwarded to a log aggregation service. This guide shows how to configure log levels, formats, and destinations. + +--- + +## 🧾 Log Structure + +Logs are emitted in JSON or text format, depending on your configuration. + +Example (JSON format): + +```json +{ + "timestamp": "2025-05-15T10:32:10Z", + "level": "INFO", + "module": "gateway_service", + "message": "Registered gateway: peer-gateway-1" +} +``` + +--- + +## 🔧 Configuring Logs + +You can control logging behavior using `.env` settings: + +| Variable | Description | Example | +| ------------ | ------------------------------- | ------------------------- | +| `LOG_LEVEL` | Minimum log level | `INFO`, `DEBUG`, `ERROR` | +| `LOG_FORMAT` | Log output format | `json` or `text` | +| `LOG_FILE` | Write logs to a file (optional) | `/var/log/mcpgateway.log` | + +--- + +## 📡 Streaming Logs (Containers) + +```bash +docker logs -f mcpgateway +# or with Podman +podman logs -f mcpgateway +``` + +--- + +## 📤 Shipping Logs to External Services + +MCP Gateway can write to stdout or a file. To forward logs to services like: + +* **ELK (Elastic Stack)** +* **LogDNA / IBM Log Analysis** +* **Datadog** +* **Fluentd / Loki** + +You can: + +* Mount log files to a sidecar container +* Use a logging agent (e.g., Filebeat) +* Pipe logs to syslog-compatible services + +--- + +## 🧪 Debug Mode + +For development, enable verbose logs by setting: + +```env +LOG_LEVEL=debug +LOG_FORMAT=text +DEBUG=true +``` + +This enables detailed request traces and internal service logs. + +--- diff --git a/docs/docs/manage/tuning.md b/docs/docs/manage/tuning.md new file mode 100644 index 0000000000..f81cb5febb --- /dev/null +++ b/docs/docs/manage/tuning.md @@ -0,0 +1,130 @@ +# Gateway Tuning Guide + +> This page collects practical levers for squeezing the most performance, reliability, and observability out of **MCP Gateway**—no matter where you run the container (Code Engine, Kubernetes, Docker Compose, Nomad, etc.). +> +> **TL;DR** +> +> 1. Tune the **runtime environment** via `.env` and configure mcpgateway to use PostgreSQL and Redis. +> 2. Adjust **Gunicorn** workers & time‑outs in `gunicorn.conf.py`. +> 3. Right‑size **CPU/RAM** for the container or spin up more instances (with shared Redis state). +> 4. Benchmark with **hey** (or your favourite load‑generator) before & after. + +--- + +## 1 · Environment variables (`.env`) + +|  Variable  |  Default  |  Why you might change it  | +| ---------------- | -------------- | ----------------------------------------------------------------------------------- | +| `AUTH_REQUIRED` | `true` | Disable for internal/behind‑VPN deployments to shave a few ms per request. | +| `JWT_SECRET_KEY` | random | Longer key ➜ slower HMAC verify; still negligible—leave as is. | +| `CACHE_TYPE` | `database` | Switch to `redis` or `memory` if your workload is read‑heavy and latency‑sensitive. | +| `DATABASE_URL` | SQLite | Move to managed PostgreSQL + connection pooling for anything beyond dev tests. | +| `HOST`/`PORT` | `0.0.0.0:4444` | Expose a different port or bind only to `127.0.0.1` behind a reverse‑proxy. | + +> **Tip**  Any change here requires rebuilding or restarting the container if you pass the file with `--env‑file`. + +--- + +## 2 · Gunicorn settings (`gunicorn.conf.py`) + +|  Knob  |  Purpose  |  Rule of thumb  | +| ------------------------ | ------------------- | ----------------------------------------------------------------- | +| `workers` | Parallel processes | `2–4 × vCPU` for CPU‑bound work; fewer if memory‑bound. | +| `threads` | Per‑process threads | Use only with `sync` worker; keeps memory low for I/O workloads. | +| `timeout` | Kill stuck worker | Set ≥ end‑to‑end model latency. E.g. 600 s for LLM calls. | +| `preload_app` | Load app once | Saves RAM; safe for pure‑Python apps. | +| `worker_class` | Async workers | `gevent` or `eventlet` for many concurrent requests / websockets. | +| `max_requests(+_jitter)` | Self‑healing | Recycle workers to mitigate memory leaks. | + +Edit the file **before** building the image, then redeploy. + +--- + +## 3 · Container resources + +| vCPU × RAM | Good for  | Notes | +| ------------ | --------------------- | -------------------------------------------------- | +| `0.5 × 1 GB` | Smoke tests / CI | Smallest footprint; likely CPU‑starved under load. | +| `1 × 4 GB` | Typical dev / staging | Handles a few hundred RPS with default 8 workers. | +| `2 × 8 GB` | Small prod | Allows \~16–20 workers; good concurrency. | +| `4 × 16 GB`+ | Heavy prod | Combine with async workers or autoscaling. | + +> Always test with **your** workload; JSON‑RPC payload size and backend model latency change the equation. + +--- + +## 4 · Performance testing + +### 4.1 Tooling: **hey** + +Install one of: + +```bash +brew install hey # macOS +sudo apt install hey # Debian/Ubuntu +# or build from source +go install github.com/rakyll/hey@latest # $GOPATH/bin must be in PATH +``` + +### 4.2 Sample load‑test script (`tests/hey.sh`) + +```bash +#!/usr/bin/env bash +# Run 10 000 requests with 200 concurrent workers. +JWT="$(cat jwt.txt)" # <- place a valid token here +hey -n 10000 -c 200 \ + -m POST \ + -T application/json \ + -H "Authorization: Bearer ${JWT}" \ + -D tests/hey/payload.json \ + http://localhost:4444/rpc +``` + +**Payload (`tests/hey/payload.json`)** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "convert_time", + "params": { + "source_timezone": "Europe/Berlin", + "target_timezone": "Europe/Dublin", + "time": "09:00" + } +} +``` + +### 4.3 Reading the output + +`hey` prints latency distribution, requests/second, and error counts. Focus on: + +* **99th percentile latency** – adjust `timeout` if it clips. +* **Errors** – 5xx under load often mean too few workers or DB connections. +* **Throughput (RPS)** – compare before/after tuning. + +### 4.4 Common bottlenecks & fixes + +| Symptom | Likely cause | Mitigation | +| ------------------------ | ----------------------------------- | ---------------------------------------------------------- | +| High % of 5xx under load | Gunicorn workers exhausted | Increase `workers`, switch to async workers, raise CPU. | +| Latency > timeout | Long model call / external API | Increase `timeout`, add queueing, review upstream latency. | +| Memory OOM | Too many workers / large batch size | Lower `workers`, disable `preload_app`, add RAM. | + +--- + +## 5 · Logging & observability + +* Set `loglevel = "debug"` in `gunicorn.conf.py` during tests; revert to `info` in prod. +* Forward `stdout`/`stderr` from the container to your platform’s log stack (e.g. `kubectl logs`, `docker logs`). +* Expose `/metrics` via Prometheus exporter (coming soon) for request timing & queue depth. + +--- + +## 6 · Security tips while tuning + +* Never commit real `JWT_SECRET_KEY`, DB passwords, or tokens—use `.env.example` as a template. +* Prefer platform secrets (K8s Secrets, Code Engine secrets) over baking creds into the image. +* If you enable `gevent`/`eventlet`, pin their versions and run **bandit** or **trivy** scans. + +--- diff --git a/docs/docs/manage/upgrade.md b/docs/docs/manage/upgrade.md new file mode 100644 index 0000000000..29a0e2db6c --- /dev/null +++ b/docs/docs/manage/upgrade.md @@ -0,0 +1,63 @@ +# Upgrading MCP Gateway and Managing Database Migrations + +This guide provides step-by-step instructions for upgrading the MCP Gateway and handling associated database migrations to ensure a smooth transition with minimal downtime. + +--- + +## 🔄 Upgrade Overview + +MCP Gateway is under active development, and while we strive for backward compatibility, it's essential to review version changes carefully when upgrading. Due to rapid iterations, documentation updates may sometimes lag. If you encounter issues, consult our [GitHub repository](https://github.com/ibm/mcp-context-forge) or reach out via GitHub Issues. + +--- + +## 🛠 Upgrade Steps + +### 1. Backup Current Configuration and Data + +Before initiating an upgrade: + +- **Export Configuration**: Backup your current configuration files. +- **Database Backup**: Create a full backup of your database to prevent data loss. + +### 2. Review Release Notes + +Check the [release notes](https://github.com/ibm/mcp-context-forge/releases) for: + +- **Breaking Changes**: Identify any changes that might affect your current setup. +- **Migration Scripts**: Look for any provided scripts or instructions for database migrations. + +### 3. Update MCP Gateway + +Depending on your deployment method: podman, docker, kubernetes, etc. + +### 4. Apply Database Migrations + +If the new version includes database schema changes: + +* **Migration Scripts**: Execute any provided migration scripts. +* **Manual Migrations**: If no scripts are provided, consult the release notes for manual migration instructions. + +### 5. Verify the Upgrade + +Post-upgrade, ensure: + +* **Service Availability**: MCP Gateway is running and accessible. +* **Functionality**: All features and integrations are working as expected. +* **Logs**: Check logs for any errors or warnings. + +--- + +## 🧪 Testing and Validation + +* **Staging Environment**: Test the upgrade process in a staging environment before applying to production. +* **Automated Tests**: Run your test suite to catch any regressions. +* **User Acceptance Testing (UAT)**: Engage end-users to validate critical workflows. + +--- + +## 📚 Additional Resources + +* [MCP Gateway GitHub Repository](https://github.com/ibm/mcp-context-forge) +* [MCP Gateway Documentation](../index.md) + +--- diff --git a/docs/docs/overview/.pages b/docs/docs/overview/.pages new file mode 100644 index 0000000000..a4608e2e76 --- /dev/null +++ b/docs/docs/overview/.pages @@ -0,0 +1,5 @@ +title: Overview +nav: + - index.md + - features.md + - ui.md diff --git a/docs/docs/overview/features.md b/docs/docs/overview/features.md new file mode 100644 index 0000000000..3c5574d1af --- /dev/null +++ b/docs/docs/overview/features.md @@ -0,0 +1,96 @@ +# Features + +MCP Gateway offers a robust feature set for integrating and managing tools, servers, prompts, and resources under the Model Context Protocol. + +--- + +## 🧠 Core Capabilities + +- **Full MCP 2025-03-26 Protocol Support** + Implements all required methods: `initialize`, `ping`, `notify`, `complete`, `createMessage`, and fallback JSON-RPC. + +- **Multi-Transport Support** + Accessible via: + - HTTP/JSON-RPC + - WebSocket (bi-directional with ping/pong) + - Server-Sent Events (SSE) + - stdio (for subprocess embedding) + +- **Unified Registry** + Maintains a centralized catalog of: + - Tools (native or REST-adapted) + - Prompts (Jinja2 templates with schema validation) + - Resources (MIME-aware, URI-addressable) + - Servers (virtual or federated) + - Federated Gateways + +--- + +## 🌐 Federation & Discovery + +- Peer discovery (mDNS or explicit list) +- Periodic health checks with failover logic +- Transparent merging of capabilities +- Federation timeouts, retries, and sync intervals configurable + +--- + +## 🛠 Tool Management + +- Register tools via REST, UI, or JSON-RPC +- Wrap any REST API, CLI command, or function +- Supports: + - JSON Schema validation + - Concurrency limits + - Rate limiting + - Retry policies + - Output filtering via JSONPath + +--- + +## 💬 Prompt Templates + +- Jinja2-powered text blocks +- Enforced schema (required/optional args) +- Versioned templates with rollback +- Used by agents and sampling calls + +--- + +## 📦 Resource Handling + +- URI-addressed resources +- MIME type detection +- LRU+TTL caching (in-memory, Redis, or DB) +- SSE-based subscriptions to dynamic resources + +--- + +## 📊 Observability + +- Structured JSON logs +- Log levels per route +- `/health` endpoint with live latency stats +- Metrics for tools, servers, prompts, and gateways + +--- + +## 🖥 Admin Interface + +- Interactive UI with full CRUD for: + - Tools + - Resources + - Prompts + - Servers + - Gateways + - Roots +- Built with HTMX, Alpine.js, and Tailwind CSS + +--- + +## 🔐 Authentication & Security + +- Supports both Basic and JWT authentication +- Bearer tokens signed with configurable secrets +- TLS verification options +- Optional anonymous/public mode (`AUTH_REQUIRED=false`) diff --git a/docs/docs/overview/index.md b/docs/docs/overview/index.md new file mode 100644 index 0000000000..551443057a --- /dev/null +++ b/docs/docs/overview/index.md @@ -0,0 +1,27 @@ +# Overview + +Welcome to the MCP Gateway documentation. + +This section introduces what the Gateway is, how it fits into the MCP ecosystem, and what core features and capabilities it offers out of the box. + +--- + +## What is MCP Gateway? + +**MCP Gateway** is an orchestration and federation layer for the [Model Context Protocol (MCP)](https://github.com/modelcontext/protocol). It provides: + +- A unified entrypoint for tools, resources, prompts, and agents +- Federation of multiple MCP servers into one composable catalog +- Protocol enforcement, health monitoring, and registry centralization +- A visual Admin UI to manage everything in real time + +Whether you're integrating REST APIs, local functions, or full LLM agents, MCP Gateway standardizes access and transport — over HTTP, WebSockets, SSE, or stdio. + +--- + +## What's in This Section + +| Page | Description | +|------|-------------| +| [Features](features.md) | Breakdown of supported features including federation, transports, and tool wrapping | +| [Admin UI](ui.md) | Screenshots and explanation of the interactive web dashboard | diff --git a/docs/docs/overview/ui.md b/docs/docs/overview/ui.md new file mode 100644 index 0000000000..ec77d720ad --- /dev/null +++ b/docs/docs/overview/ui.md @@ -0,0 +1,58 @@ +# Admin UI + +MCP Gateway includes a built-in Admin UI for managing all entities in real time via a web browser. + +--- + +## 🖥️ Accessing the UI + +After launching the gateway (`make serve` or `make podman-run`), open your browser and go to: + +[http://localhost:4444/admin](http://localhost:4444/admin) - or the corresponding URL / port / protocol (ex: https when launching with `make podman-run-ssl`) + +Login using the `BASIC_AUTH_USER` and `BASIC_AUTH_PASSWORD` set in your `.env`. + +--- + +## 🧭 UI Overview + +The Admin UI is built with **HTMX**, **Alpine.js**, and **Tailwind CSS**, offering a dynamic, SPA-like experience without JavaScript bloat. + +It provides tabbed access to: + +- **Servers Catalog**: Define or edit MCP servers (real or virtual) +- **Tools**: Register REST or native tools, configure auth/rate limits, test responses +- **Resources**: Add templated or static resources, set MIME types, enable caching +- **Prompts**: Define Jinja2 prompt templates with argument schemas and preview rendering +- **Gateways**: View and manage federated peers, toggle activity status +- **Roots**: Register root URIs for agent or resource scoping +- **Metrics**: Real-time usage and performance metrics for all entities + +--- + +## ✍️ Common Actions + +| Action | How | +|--------|-----| +| Register a tool | Use the Tools tab → Add Tool form | +| View prompt output | Go to Prompts → click View | +| Toggle server activity | Use the “Activate/Deactivate” buttons in Servers tab | +| Delete a resource | Navigate to Resources → click Delete (after confirming) | + +All actions are reflected in the live API via `/tools`, `/prompts`, etc. + +--- + +## 🔐 Auth + JWT from UI + +Upon successful login, the UI automatically sets a secure JWT token as an HTTP-only cookie (`jwt_token`). + +This token is reused for all Admin API calls from within the UI. + +--- + +## 🔄 Live Reloading (Dev Only) + +If running in development mode (`DEV_MODE=true` or `make run`), changes to templates and routes reload automatically. + +--- diff --git a/docs/docs/using/.pages b/docs/docs/using/.pages new file mode 100644 index 0000000000..f21dbb1fdf --- /dev/null +++ b/docs/docs/using/.pages @@ -0,0 +1,6 @@ +title: Usage +nav: + - index.md + - mcpgateway-wrapper.md + - Clients: clients + - Agents: agents diff --git a/docs/docs/using/agents/.pages b/docs/docs/using/agents/.pages new file mode 100644 index 0000000000..78218d648b --- /dev/null +++ b/docs/docs/using/agents/.pages @@ -0,0 +1,11 @@ +title: Agents +nav: + - index.md + - langchain.md + - langgraph.md + - crewai.md + - bee.md + - autogen.md + - semantic-kernel.md + - openai-sdk.md + - llamaindex.md diff --git a/docs/docs/using/agents/autogen.md b/docs/docs/using/agents/autogen.md new file mode 100644 index 0000000000..7b2597bbac --- /dev/null +++ b/docs/docs/using/agents/autogen.md @@ -0,0 +1,18 @@ +# AutoGen Integration with MCP Gateway + +[AutoGen](https://github.com/microsoft/autogen) is an open-source framework from Microsoft for building multi-agent systems. It supports tool calling and dynamic agent coordination. + +--- + +## 🔧 MCP Support + +Experimental support for MCP integration is available via custom `ToolAgent` wrappers that call MCP tools via HTTP or `mcpgateway-wrapper`. + +A full guide is coming soon. For now, you can use `requests` or `httpx` to call MCP Gateway endpoints from AutoGen agents. + +--- + +## 🔗 Resources + +- [AutoGen GitHub](https://github.com/microsoft/autogen) +- [AutoGen Docs](https://microsoft.github.io/autogen) diff --git a/docs/docs/using/agents/bee.md b/docs/docs/using/agents/bee.md new file mode 100644 index 0000000000..257e0d5324 --- /dev/null +++ b/docs/docs/using/agents/bee.md @@ -0,0 +1,110 @@ +# Bee Agent Framework Integration with MCP Gateway + +The Bee Agent Framework is an open-source platform developed by IBM for building, deploying, and managing AI agents at scale. Integrating Bee with the Model Context Protocol (MCP) allows agents to dynamically discover and utilize tools hosted on MCP servers, enhancing their capabilities and flexibility. + +--- + +## 🧰 Key Features + +- **Dynamic Tool Discovery**: Agents can fetch available tools from MCP servers in real-time. +- **Standardized Communication**: Utilizes the open MCP standard for consistent tool integration. +- **Multi-Server Support**: Interact with tools defined on multiple MCP servers simultaneously. +- **Human-in-the-Loop**: Incorporate human feedback into agent workflows for improved decision-making. + +--- + +## 🛠 Installation + +To use MCP tools in the Bee Agent Framework, follow these steps: + +1. **Clone the Bee Agent Framework Repository**: + + ```bash + git clone https://github.com/i-am-bee/bee-agent-framework.git + cd bee-agent-framework +``` + +2. **Install Dependencies**: + + ```bash + yarn install + ``` + +3. **Set Up the Environment**: + + Ensure you have Node.js and Yarn installed. You may also need to set environment variables for your MCP server: + + ```bash + export MCP_GATEWAY_BASE_URL=http://localhost:4444 + export MCP_AUTH_USER=admin + export MCP_AUTH_PASS=changeme + ``` + +--- + +## 🔗 Connecting to MCP Gateway + +Bee provides a native `MCPTool` class to simplify integration with MCP servers. Here's how to set it up: + +1. **Import the MCPTool Class**: + + ```javascript + import { MCPTool } from 'bee-agent-framework/tools/mcp'; + ``` + +2. **Configure the MCPTool**: + + ```javascript + const mcpTool = new MCPTool({ + baseUrl: process.env.MCP_GATEWAY_BASE_URL, + auth: { + username: process.env.MCP_AUTH_USER, + password: process.env.MCP_AUTH_PASS, + }, + }); + ``` + +3. **Register the Tool with Your Agent**: + + ```javascript + agent.registerTool(mcpTool); + ``` + +This setup allows your Bee agent to discover and invoke tools from the specified MCP server dynamically. + +--- + +## 🤖 Creating an Agent + +After setting up the MCPTool, you can create a Bee agent: + +```javascript +import { Agent } from 'bee-agent-framework'; + +const agent = new Agent({ + name: 'Data Analyst', + tools: [mcpTool], +}); +``` + +--- + +## 🧪 Using the Agent + +Once the agent is created, you can assign tasks and execute them: + +```javascript +agent.runTask('Generate a sales report for Q1 2025'); +``` + +The agent will utilize tools from the MCP server to accomplish the task. + +--- + +## 📚 Additional Resources + +* [Bee Agent Framework Documentation](https://i-am-bee.github.io/beeai-framework/#/) +* [Bee Agent Framework GitHub Repository](https://github.com/i-am-bee/bee-agent-framework) +* [Model Context Protocol Overview](https://modelcontextprotocol.io/) + +--- diff --git a/docs/docs/using/agents/crewai.md b/docs/docs/using/agents/crewai.md new file mode 100644 index 0000000000..64a59c1ef9 --- /dev/null +++ b/docs/docs/using/agents/crewai.md @@ -0,0 +1,20 @@ +# CrewAI Integration with MCP Gateway + +CrewAI is a multi-agent orchestration framework that enables AI agents to collaborate on complex tasks. Integrating CrewAI with the Model Context Protocol (MCP) allows agents to dynamically discover and utilize tools hosted on MCP servers, enhancing their capabilities and flexibility. + +--- + +## 🧰 Key Features + +- **Dynamic Tool Discovery**: Agents can fetch available tools from MCP servers in real-time. +- **Standardized Communication**: Utilizes the open MCP standard for consistent tool integration. +- **Multi-Server Support**: Interact with tools defined on multiple MCP servers simultaneously. + +--- + +## 🛠 Installation + +To use MCP tools in CrewAI, install the `crewai-tools` package with MCP support: + +```bash +pip install "crewai-tools[mcp]" diff --git a/docs/docs/using/agents/index.md b/docs/docs/using/agents/index.md new file mode 100644 index 0000000000..49de8a86ac --- /dev/null +++ b/docs/docs/using/agents/index.md @@ -0,0 +1,36 @@ +# Agent Integrations + +This section provides guidance on integrating various AI agent frameworks with the Model Context Protocol (MCP) Gateway. MCP enables agents to dynamically discover and utilize tools across multiple servers, enhancing their capabilities and flexibility. + +--- + +## 🧠 Supported Agent Frameworks + +- [LangChain](langchain.md): Utilize MCP tools within LangChain agents using the `langchain-mcp-adapters` package. +- [LangGraph](langgraph.md): Integrate MCP tools into LangGraph agents for advanced workflow orchestration. +- [CrewAI](crewai.md): Connect CrewAI agents to MCP servers using the `crewai-tools` library. +- [Bee Agent Framework](bee.md): Leverage MCP tools within the Bee Agent Framework for scalable agent deployments. +- [AutoGen](autogen.md): Integrate MCP tools with AutoGen agents using the `autogen-ext-mcp` package. +- [LlamaIndex](llamaindex.md): Incorporate MCP tools into LlamaIndex workflows for enhanced data retrieval and question answering. +- [OpenAI Agents SDK](openai-sdk.md): Utilize MCP tools within OpenAI's Agents SDK for building AI agents with standardized tool access. +- [Semantic Kernel](semantic-kernel.md): Connect Semantic Kernel agents to MCP servers for enriched context and tool integration. + +--- + +## 🔍 Overview + +Each integration guide includes: + +- **Installation Instructions**: Step-by-step setup for the respective agent framework. +- **Configuration Details**: How to connect the agent to the MCP Gateway, including authentication and transport options. +- **Usage Examples**: Sample code demonstrating how to invoke MCP tools within the agent's workflow. +- **Additional Resources**: Links to official documentation and repositories for further reference. + +--- + +## 📚 Additional Resources + +- [Model Context Protocol Overview](https://modelcontextprotocol.io/) +- [MCP Gateway Documentation](../index.md) + +--- diff --git a/docs/docs/using/agents/langchain.md b/docs/docs/using/agents/langchain.md new file mode 100644 index 0000000000..0b622c6515 --- /dev/null +++ b/docs/docs/using/agents/langchain.md @@ -0,0 +1,79 @@ +# LangChain Integration with MCP Gateway + +LangChain is a framework for developing applications powered by language models. Integrating LangChain with the Model Context Protocol (MCP) allows agents to utilize tools defined across one or more MCP servers, enabling seamless interaction with external data sources and services. + +--- + +## 🧰 Key Features + +- **Dynamic Tool Access**: Connects to MCP servers to fetch available tools in real time. +- **Multi-Server Support**: Interact with tools defined on multiple MCP servers simultaneously. +- **Standardized Communication**: Utilizes the open MCP standard for consistent tool integration. + +--- + +## 🛠 Installation + +To use MCP tools in LangChain, install the `langchain-mcp-adapters` package: + +```bash +pip install langchain-mcp-adapters +``` + +--- + +## 🔗 Connecting to MCP Gateway + +Here's how to set up a connection to your MCP Gateway: + +```python +from langchain_mcp_adapters.client import MultiServerMCPClient +from langgraph.prebuilt import create_react_agent + +client = MultiServerMCPClient( + { + "gateway": { + "url": "http://localhost:4444/mcp", + "transport": "streamable_http", + } + } +) +``` + +Replace `"http://localhost:4444/mcp"` with the URL of your MCP Gateway. + +--- + +## 🤖 Creating an Agent + +After setting up the client, you can create a LangChain agent: + +```python +agent = create_react_agent( + tools=client.get_tools(), + llm=your_language_model, +) +``` + +Replace `your_language_model` with your configured language model instance. + +--- + +## 🧪 Using the Agent + +Once the agent is created, you can use it to perform tasks: + +```python +response = agent.run("Use the 'weather' tool to get the forecast for Dublin.") +print(response) +``` + +--- + +## 📚 Additional Resources + +* [LangChain MCP Adapters Documentation](https://langchain-ai.github.io/langgraph/agents/mcp/) +* [LangChain GitHub Repository](https://github.com/langchain-ai/langchain) +* [Model Context Protocol Overview](https://modelcontextprotocol.io/) + +--- diff --git a/docs/docs/using/agents/langgraph.md b/docs/docs/using/agents/langgraph.md new file mode 100644 index 0000000000..c15be25054 --- /dev/null +++ b/docs/docs/using/agents/langgraph.md @@ -0,0 +1,79 @@ +# LangGraph Integration with MCP Gateway + +LangGraph is a framework for developing applications powered by language models. Integrating LangGraph with the Model Context Protocol (MCP) allows agents to utilize tools defined across one or more MCP servers, enabling seamless interaction with external data sources and services. + +--- + +## 🧰 Key Features + +- **Dynamic Tool Access**: Connects to MCP servers to fetch available tools in real time. +- **Multi-Server Support**: Interact with tools defined on multiple MCP servers simultaneously. +- **Standardized Communication**: Utilizes the open MCP standard for consistent tool integration. + +--- + +## 🛠 Installation + +To use MCP tools in LangGraph, install the `langchain-mcp-adapters` package: + +```bash +pip install langchain-mcp-adapters +``` + +--- + +## 🔗 Connecting to MCP Gateway + +Here's how to set up a connection to your MCP Gateway: + +```python +from langchain_mcp_adapters.client import MultiServerMCPClient +from langgraph.prebuilt import create_react_agent + +client = MultiServerMCPClient( + { + "gateway": { + "url": "http://localhost:4444/mcp", + "transport": "streamable_http", + } + } +) +``` + +Replace `"http://localhost:4444/mcp"` with the URL of your MCP Gateway. + +--- + +## 🤖 Creating an Agent + +After setting up the client, you can create a LangGraph agent: + +```python +agent = create_react_agent( + tools=client.get_tools(), + llm=your_language_model, +) +``` + +Replace `your_language_model` with your configured language model instance. + +--- + +## 🧪 Using the Agent + +Once the agent is created, you can use it to perform tasks: + +```python +response = agent.run("Use the 'weather' tool to get the forecast for Dublin.") +print(response) +``` + +--- + +## 📚 Additional Resources + +* [LangGraph MCP Integration Documentation](https://langchain-ai.github.io/langgraph/agents/mcp/) +* [LangChain MCP Adapters GitHub Repository](https://github.com/langchain-ai/langchain-mcp-adapters) +* [Model Context Protocol Overview](https://modelcontextprotocol.io/) + +--- diff --git a/docs/docs/using/agents/llamaindex.md b/docs/docs/using/agents/llamaindex.md new file mode 100644 index 0000000000..7bc9a5c826 --- /dev/null +++ b/docs/docs/using/agents/llamaindex.md @@ -0,0 +1,11 @@ +# LlamaIndex Integration with MCP Gateway + +LlamaIndex is a framework for building retrieval-augmented generation (RAG) pipelines. + +--- + +## 🔧 MCP Support + +You can wrap tool calls from MCP Gateway as query engines, retrievers, or tool nodes inside LlamaIndex. + +A dedicated `ToolRetriever` adapter is under development to support direct MCP tool discovery. diff --git a/docs/docs/using/agents/openai-sdk.md b/docs/docs/using/agents/openai-sdk.md new file mode 100644 index 0000000000..68a407249a --- /dev/null +++ b/docs/docs/using/agents/openai-sdk.md @@ -0,0 +1,15 @@ + +# OpenAI Agents SDK + MCP Gateway + +OpenAI's Agents SDK supports structured tool use and multi-modal workflows. MCP Gateway can serve as a unified tool registry for OpenAI agents. + +--- + +## 🔧 Integration + +OpenAI SDK has native support for MCP. + +## 🔗 Resources + +- [OpenAI Agents SDK](https://platform.openai.com/docs/assistants/overview) +- [MCP Tool Protocol](https://modelcontextprotocol.io/) diff --git a/docs/docs/using/agents/semantic-kernel.md b/docs/docs/using/agents/semantic-kernel.md new file mode 100644 index 0000000000..753a54babf --- /dev/null +++ b/docs/docs/using/agents/semantic-kernel.md @@ -0,0 +1,18 @@ +# Semantic Kernel Integration with MCP Gateway + +[Semantic Kernel](https://aka.ms/semantic-kernel) is a Microsoft OSS framework for building AI-first apps. + +--- + +## 🔧 MCP Integration + +Support for external tools via REST allows you to call MCP tools from SK plugins using `HttpFunction`. + +Define a plugin that points to MCP Gateway's `/tools/invoke` and pass arguments as JSON. + +--- + +## 🔗 Resources + +- [Semantic Kernel GitHub](https://github.com/microsoft/semantic-kernel) +- [Using REST APIs in SK](https://learn.microsoft.com/en-us/semantic-kernel/) diff --git a/docs/docs/using/clients/.pages b/docs/docs/using/clients/.pages new file mode 100644 index 0000000000..a661f111f6 --- /dev/null +++ b/docs/docs/using/clients/.pages @@ -0,0 +1,8 @@ +title: Clients +nav: + - index.md + - mcp-inspector.md + - claude-desktop.md + - cline.md + - continue.md + - openwebui.md diff --git a/docs/docs/using/clients/claude-desktop.md b/docs/docs/using/clients/claude-desktop.md new file mode 100644 index 0000000000..6b33ce8969 --- /dev/null +++ b/docs/docs/using/clients/claude-desktop.md @@ -0,0 +1,72 @@ +# Claude Desktop + +[Claude Desktop](https://www.anthropic.com/index/claude-desktop) is a desktop application that supports MCP integration via stdio. You can configure it to launch `mcpgateway-wrapper`, enabling Claude to access all tools registered in MCP Gateway. + +--- + +## 🖥️ Where to Configure + +Depending on your OS, edit the Claude configuration file: + +- **macOS**: + `~/Library/Application Support/Claude/claude_desktop_config.json` + +- **Windows**: + `%APPDATA%/Claude/claude_desktop_config.json` + +--- + +## ⚙️ Example Configuration + +Add this block to the `"mcpServers"` section of your config: + +```json +"mcpServers": { + "mcpgateway-wrapper": { + "command": "uv", + "args": [ + "--directory", + "/path/to/mcpgateway-wrapper", + "run", + "mcpgateway-wrapper" + ], + "env": { + "MCP_GATEWAY_BASE_URL": "http://localhost:4444", + "MCP_SERVER_CATALOG_URLS": "http://localhost:4444/servers/2", + "MCP_AUTH_USER": "admin", + "MCP_AUTH_PASS": "changeme" + } + } +} +``` + +> 🔁 Adjust `path/to/mcpgateway-wrapper` and server ID as needed. + +--- + +## 🧪 Test it in Claude + +Once Claude launches: + +1. Choose the `mcpgateway-wrapper` backend +2. Type a tool invocation (e.g., `weather`, `hello`, etc.) +3. The tool should be fetched from the Gateway and executed dynamically + +--- + +## 🚀 Advanced: Pre-installed Wrapper Mode + +If you’ve published the wrapper to PyPI or have it globally installed: + +```json +"mcpServers": { + "mcpgateway-wrapper": { + "command": "uvx", + "args": ["mcpgateway-wrapper"] + } +} +``` + +This assumes your environment variables are managed globally or set in the terminal session launching Claude. + +--- diff --git a/docs/docs/using/clients/cline.md b/docs/docs/using/clients/cline.md new file mode 100644 index 0000000000..77414615fe --- /dev/null +++ b/docs/docs/using/clients/cline.md @@ -0,0 +1,86 @@ +# Cline (VS Code Extension) + +[Cline](https://cline.bot/) is a Visual Studio Code extension that brings AI-powered coding assistance directly into your editor. It supports the Model Context Protocol (MCP), enabling seamless integration with MCP-compatible servers like MCP Gateway. + +--- + +## 🧰 Key Features + +- **AI-Powered Coding**: Leverages advanced AI models (e.g., Claude 3.5 Sonnet, DeepSeek Chat) for code generation, editing, and debugging. +- **MCP Integration**: Connects to MCP servers to discover and utilize tools dynamically. +- **Terminal and Browser Access**: Executes terminal commands and performs browser operations with user permission. +- **Custom Tools**: Supports adding custom tools via MCP for extended functionality. + +--- + +## 🛠 Installation + +1. **Install Cline Extension**: + - Open VS Code. + - Navigate to the Extensions view (`Ctrl+Shift+X` or `Cmd+Shift+X`). + - Search for "Cline" and click "Install". + +2. **Sign In to Cline**: + - Click the Cline icon in the Activity Bar. + - Follow the prompts to sign in or create a new account at [app.cline.bot](https://app.cline.bot/). + - New users receive free credits; no credit card required. + +--- + +## 🔗 Connecting to MCP Gateway + +To integrate Cline with your MCP Gateway: + +1. **Configure MCP Server**: + - Open the Cline settings in VS Code. + - Navigate to the MCP Servers section. + - Add a new MCP server with the following configuration: + + ```json + { + "name": "MCP Gateway", + "url": "http://localhost:4444", + "auth": { + "type": "basic", + "username": "admin", + "password": "changeme" + } + } + ``` + + - Replace the URL, username, and password with your MCP Gateway's details. + +2. **Enable the MCP Server**: + - Ensure the newly added MCP server is enabled in the Cline settings. + +3. **Verify Connection**: + - In the Cline interface, navigate to the MCP Servers section. + - Confirm that the MCP Gateway server is listed and shows a green status indicator. + +--- + +## 🧪 Using MCP Tools in Cline + +Once connected: + +- **Discover Tools**: Cline will automatically fetch and list available tools from the MCP Gateway. +- **Invoke Tools**: Use natural language prompts in Cline to invoke tools. For example: + - "Run the `hello_world` tool with the argument `name: Alice`." +- **Monitor Responses**: Cline will display the tool's output directly within the chat interface. + +--- + +## 📝 Tips for Effective Use + +- **.clinerules File**: Create a `.clinerules` file in your project root to define project-specific behaviors and instructions for Cline. +- **Custom Instructions**: Utilize Cline's Custom Instructions feature to tailor its behavior across all projects. +- **Model Selection**: Choose the AI model that best fits your project's needs within the Cline settings. + +--- + +## 📚 Additional Resources + +- [Cline Official Website](https://cline.bot/) +- [Cline Documentation](https://docs.cline.bot/) + +--- diff --git a/docs/docs/using/clients/continue.md b/docs/docs/using/clients/continue.md new file mode 100644 index 0000000000..d2feff1418 --- /dev/null +++ b/docs/docs/using/clients/continue.md @@ -0,0 +1,91 @@ +# Continue (VS Code Extension) + +[Continue](https://www.continue.dev/) is an open-source AI code assistant available as a Visual Studio Code extension. It supports the Model Context Protocol (MCP), allowing seamless integration with MCP-compatible servers like MCP Gateway. + +--- + +## 🧰 Key Features + +- **AI-Powered Coding**: Provides code completions, edits, and chat-based assistance. +- **MCP Integration**: Connects to MCP servers to discover and utilize tools dynamically. +- **Customizable Models**: Supports various AI models, including local and remote options. +- **Contextual Awareness**: Understands your codebase to provide relevant suggestions. + +--- + +## 🛠 Installation + +1. **Install Continue Extension**: + - Open VS Code. + - Navigate to the Extensions view (`Ctrl+Shift+X` or `Cmd+Shift+X`). + - Search for "Continue" and click "Install". + +2. **Configure Continue**: + - Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P`). + - Select "Continue: Open Config". + - This opens the `~/.continue/config.json` file. + +--- + +## 🔗 Connecting to MCP Gateway + +To integrate Continue with your MCP Gateway: + +1. **Add MCP Server Configuration**: + In your `~/.continue/config.json`, add the following under the `experimental` section: + + ```json + { + "experimental": { + "modelContextProtocolServer": { + "transport": { + "type": "stdio", + "command": "uvx", + "args": ["mcpgateway-wrapper"] + } + } + } + } +``` + +Replace `"mcpgateway-wrapper"` with the appropriate command or path to your MCP Gateway server if different. + +2. **Set Environment Variables**: + Ensure the following environment variables are set: + + * `MCP_GATEWAY_BASE_URL`: Base URL of your MCP Gateway (e.g., `http://localhost:4444`). + * `MCP_SERVER_CATALOG_URLS`: URL(s) to the server catalog(s) (e.g., `http://localhost:4444/servers/2`). + * `MCP_AUTH_USER`: Username for authentication (e.g., `admin`). + * `MCP_AUTH_PASS`: Password for authentication (e.g., `changeme`). + + You can set these in your system environment or within the Continue configuration if supported. + +--- + +## 🧪 Using MCP Tools in Continue + +Once configured: + +* **Discover Tools**: Continue will automatically fetch and list available tools from the MCP Gateway. +* **Invoke Tools**: Use natural language prompts in Continue to invoke tools. For example: + + * "Run the `hello_world` tool with the argument `name: Alice`." +* **Monitor Responses**: Continue will display the tool's output directly within the chat interface. + +--- + +## 📝 Tips for Effective Use + +* **Custom Instructions**: Utilize Continue's Custom Instructions feature to tailor its behavior across all projects. +* **Model Selection**: Choose the AI model that best fits your project's needs within the Continue settings. +* **.continue/config.json**: Customize your Continue experience by editing this configuration file. + +--- + +## 📚 Additional Resources + +* [Continue Official Website](https://www.continue.dev/) +* [Continue Documentation](https://docs.continue.dev/) +* [Continue GitHub Repository](https://github.com/continuedev/continue) + +--- diff --git a/docs/docs/using/clients/index.md b/docs/docs/using/clients/index.md new file mode 100644 index 0000000000..e4eaf296ba --- /dev/null +++ b/docs/docs/using/clients/index.md @@ -0,0 +1,38 @@ +# MCP Clients + +MCP Gateway is compatible with any client that speaks the [Model Context Protocol (MCP)](https://github.com/modelcontext/protocol). This section documents tested clients, their configuration, and any integration tips. + +--- + +## 🔌 Client Types + +There are two ways clients typically connect: + +- **Direct to Gateway** (HTTP/SSE/WS) +- **Via `mcpgateway-wrapper`** (stdio transport, especially for LLM apps) + +--- + +## ✅ Compatible Clients + +| Client | Type | Notes | +|--------|------|-------| +| [Claude Desktop](claude-desktop.md) | UI | Configure to launch `mcpgateway-wrapper` via JSON | +| [Cline](cline.md) | CLI | Supports stdio or direct MCP over HTTP | +| [Continue](continue.md) | VSCode plugin | MCP plugin support | +| [MCP Inspector](mcp-inspector.md) | Web debugger | Great for manual testing and exploring protocol features | + +Each of these tools can consume the MCP protocol and dynamically detect tools from the Gateway. + +--- + +## 📁 What's in This Section + +| Page | Description | +|------|-------------| +| [Claude Desktop](claude-desktop.md) | How to connect Claude to MCP Gateway via wrapper | +| [Cline](cline.md) | Using the CLI tool for invoking tools or prompts | +| [Continue](continue.md) | Integrating with the VSCode plugin | +| [MCP Inspector](mcp-inspector.md) | Launch and test the Gateway or wrapper via a web debugger | + +--- diff --git a/docs/docs/using/clients/mcp-inspector.md b/docs/docs/using/clients/mcp-inspector.md new file mode 100644 index 0000000000..714425f655 --- /dev/null +++ b/docs/docs/using/clients/mcp-inspector.md @@ -0,0 +1,64 @@ +# MCP Inspector + +[MCP Inspector](https://www.npmjs.com/package/@modelcontextprotocol/inspector) is a visual debugging tool for the Model Context Protocol. It connects to MCP-compliant servers (like `mcpgateway-wrapper` or MCP Gateway directly) and allows you to: + +- Inspect available tools +- Execute tool invocations +- View the full JSON-RPC/MCP traffic +- Simulate prompt rendering or resource access (future) + +--- + +## 🚀 Launching MCP Inspector + +If you have Node.js installed, you can launch it via `npx`: + +```bash +npx @modelcontextprotocol/inspector \ + uv --directory path/to/mcpgateway-wrapper \ + run mcpgateway-wrapper +``` + +> This will: +> +> * Start the wrapper +> * Open a local Inspector session in your browser + +--- + +## 🔧 Inspector Features + +* 📜 View registered tools in real time +* 🧪 Send test completions or invocations +* 👀 Observe request/response JSON as it flows through the system +* 🔁 Replay or modify previous messages +* 🧵 View sampling messages (when streaming is supported) + +--- + +## 🔐 Auth & Config + +Ensure you provide the necessary `MCP_AUTH_USER` and `MCP_AUTH_PASS` as environment variables if your gateway requires authentication: + +```bash +export MCP_GATEWAY_BASE_URL=http://localhost:4444 +export MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/2 +export MCP_AUTH_USER=admin +export MCP_AUTH_PASS=changeme +``` + +Then run the Inspector again. + +--- + +## 🌐 Connect to Live Gateway + +You can also connect directly to MCP Gateway without the wrapper: + +```bash +npx @modelcontextprotocol/inspector --url http://localhost:4444 +``` + +> This will query available tools, prompts, and metadata from the root. + +--- diff --git a/docs/docs/using/clients/openwebui.md b/docs/docs/using/clients/openwebui.md new file mode 100644 index 0000000000..2306941b4f --- /dev/null +++ b/docs/docs/using/clients/openwebui.md @@ -0,0 +1,98 @@ +# OpenWebUI Integration with MCP Gateway + +OpenWebUI is a self-hosted, extensible interface for interacting with large language models (LLMs). Integrating OpenWebUI with the Model Context Protocol (MCP) allows you to enhance your AI workflows by leveraging tools and resources provided by MCP servers. + +--- + +## 🔌 Integration Overview + +OpenWebUI supports integration with external tools via OpenAPI specifications. MCP Gateway exposes its tools through OpenAPI-compatible endpoints, enabling seamless integration with OpenWebUI. + +--- + +## 🛠️ Prerequisites + +- **OpenWebUI**: Ensure you have OpenWebUI installed and running. Refer to the [OpenWebUI documentation](https://docs.openwebui.com/) for installation instructions. +- **MCP Gateway**: Set up and run the MCP Gateway. Detailed setup instructions can be found in the [MCP Gateway documentation](https://modelcontextprotocol.io/). + +--- + +## 🔗 Connecting MCP Tools to OpenWebUI + +### 1. Launch MCP Gateway + +Start the MCP Gateway to expose its tools via OpenAPI endpoints. For example: + +```bash +uv run mcpgateway +```` + +Ensure that the MCP Gateway is accessible at a known URL, such as `http://localhost:4444`. + +### 2. Identify MCP Tool Endpoints + +Determine the specific tool endpoints provided by the MCP Gateway. These endpoints follow the OpenAPI specification and are typically accessible at URLs like: + +``` +http://localhost:4444/tools/ +``` + +Replace `` with the actual name of the tool you wish to integrate. + +### 3. Add MCP Tools to OpenWebUI + +#### a. Access OpenWebUI Settings + +* Navigate to the OpenWebUI interface in your browser. +* Click on the ⚙️ **Settings** icon. + +#### b. Add a New Tool Server + +* In the **Settings** menu, locate the **Tools** section. +* Click on the ➕ **Add Tool Server** button. +* Enter the URL of the MCP tool endpoint (e.g., `http://localhost:4444/tools/`). +* Click **Save** to register the tool. + +Repeat this process for each MCP tool you wish to integrate. + +--- + +## 🧪 Using MCP Tools in OpenWebUI + +Once the MCP tools are registered: + +* **Enable Tools in Chat**: In the chat interface, click on the ➕ icon to view available tools. Toggle the desired MCP tools to enable them for the current session. +* **Invoke Tools**: Interact with the AI model as usual. When appropriate, the model will utilize the enabled MCP tools to fulfill your requests. + +--- + +## ⚙️ Advanced Configuration + +### Global Tool Servers + +To make MCP tools available to all users: + +* Navigate to **Admin Settings** > **Tools**. +* Add the MCP tool endpoints as described above. +* These tools will now be accessible to all users, subject to individual activation in their chat sessions. + +### Native Function Calling + +OpenWebUI supports native function calling for tools: + +* In the chat interface, go to **Chat Controls** > **Advanced Params**. +* Set the **Function Calling** parameter to `Native`. +* This enables more structured interactions between the AI model and the tools. + +--- + +## 🧰 Additional Resources + +* [OpenWebUI Documentation](https://docs.openwebui.com/) +* [MCP Gateway Documentation](https://modelcontextprotocol.io/) +* [OpenWebUI GitHub Repository](https://github.com/open-webui/open-webui) +* [MCP Gateway GitHub Repository](https://github.com/mcp-ecosystem/mcp-gateway) + +--- + +By integrating MCP tools into OpenWebUI, you can enhance your AI assistant's capabilities, enabling it to perform a wider range of tasks by leveraging the diverse tools provided by the MCP ecosystem. diff --git a/docs/docs/using/index.md b/docs/docs/using/index.md new file mode 100644 index 0000000000..7099a90685 --- /dev/null +++ b/docs/docs/using/index.md @@ -0,0 +1,36 @@ +# Using MCP Gateway + +This section focuses on how to use MCP Gateway effectively as a developer, integrator, or end user. + +--- + +## 👨‍💻 Typical Use Cases + +- You want to expose tools, prompts, or resources via MCP. +- You want to use `mcpgateway-wrapper` to connect to any MCP Gateway service using `stdio`, while still supporting authentication to the gateway. +- You’re building a client or agent framework that speaks the MCP protocol. +- You want to consume Gateway APIs from an LLM agent, browser app, or CLI tool. + +--- + +## 📚 What You'll Find in This Section + +| Page | Description | +|------|-------------| +| [mcpgateway-wrapper](mcpgateway-wrapper.md) | Wrap CLI tools or subprocesses to expose them via SSE/stdio | +| [Clients](clients/index.md) | Compatible UIs and developer tools | +| [Agents](agents/index.md) | LangChain, LangGraph, CrewAI, and other frameworks | + +--- + +## 🔑 Authentication Reminder + +All Gateway usage requires authentication unless `AUTH_REQUIRED=false`. Refer to: + +```bash +curl -H "Authorization: Bearer $TOKEN" http://localhost:4444/tools +``` + +Or use Basic Auth for the Admin UI and `/admin` routes. + +--- diff --git a/docs/docs/using/mcpgateway-wrapper.md b/docs/docs/using/mcpgateway-wrapper.md new file mode 100644 index 0000000000..0c512bc8af --- /dev/null +++ b/docs/docs/using/mcpgateway-wrapper.md @@ -0,0 +1,146 @@ +# STDIO Wrapper + +`mcpgateway-wrapper` acts as a lightweight **MCP-compatible stdio server** that dynamically mirrors tools from a live [MCP Gateway](../overview/index.md). It allows any client that supports the MCP protocol — such as Claude Desktop, Cline, or Continue — to invoke tools directly via the Gateway. + +--- + +## 🔑 Key Features + +- **Dynamic Tool Access** + Automatically fetches all tools from a given MCP Gateway server catalog in real time. + +- **Centralized Gateway Integration** + Exposes all tools managed by your MCP Gateway under a single stdio-compatible interface. + +- **Full MCP Protocol Support** + Responds to `initialize`, `ping`, `notify`, `complete`, and `createMessage` via stdio transport. + +- **Tool Invocation** + All tool calls are proxied to the Gateway's tool registry via HTTP. + +- **Extensible** + Future support for Prompts and Resources is planned. + +--- + +## ⚙️ Components + +### ✅ Tools +Fetched from the configured server catalog (`/servers/{id}`) and exposed dynamically. + +### 🚧 Resources (Coming Soon) +Will mirror resources registered on the Gateway. + +### 🚧 Prompts (Coming Soon) +Will fetch and expose prompt templates via the MCP interface. + +--- + +## 🚀 Quickstart + +### 1. Change to the wrapper directory + +```bash +cd mcpgateway-wrapper +``` + +### 2. Integrate with Claude Desktop + +On macOS: + +```bash +~/Library/Application Support/Claude/claude_desktop_config.json +``` + +On Windows: + +```bash +%APPDATA%/Claude/claude_desktop_config.json +``` + +Add a new server block: + +```json +"mcpServers": { + "mcpgateway-wrapper": { + "command": "uv", + "args": [ + "--directory", + "path-to-mcpgateway-wrapper", + "run", + "mcpgateway-wrapper" + ], + "env": { + "MCP_GATEWAY_BASE_URL": "http://localhost:4444", + "MCP_SERVER_CATALOG_URLS": "http://localhost:4444/servers/2", + "MCP_AUTH_USER": "admin", + "MCP_AUTH_PASS": "changeme" + } + } +} +``` + +> Replace `path-to-mcpgateway-wrapper` with the actual folder path. + +### ✅ Environment Variables + +| Variable | Purpose | +| ------------------------- | -------------------------------------------------- | +| `MCP_GATEWAY_BASE_URL` | Base URL to your MCP Gateway (e.g. localhost:4444) | +| `MCP_SERVER_CATALOG_URLS` | One or more `/servers/{id}` catalog URLs | +| `MCP_AUTH_USER` | Username for HTTP Basic authentication | +| `MCP_AUTH_PASS` | Password for HTTP Basic authentication | + +--- + +## 🐍 Local Development + +To run locally: + +```bash +uv run mcpgateway-wrapper +``` + +Or debug using the MCP Inspector: + +```bash +npx @modelcontextprotocol/inspector uv --directory "path-to-wrapper" run mcpgateway-wrapper +``` + +--- + +## 🏗 Build + +Use [`uv`](https://github.com/astral-sh/uv) to manage builds: + +```bash +uv sync # Install dependencies +``` + +--- + +## 📝 Example Use Case + +Once launched, any MCP-compatible client (e.g. Claude Desktop or Cline) can call: + +```json +{ + "method": "hello_world", + "params": { "name": "Alice" } +} +``` + +And the wrapper will: + +1. Match the method to a tool in the Gateway's registry +2. Send a tool invocation request to the Gateway +3. Return the result via stdout + +--- + +## 🔮 Planned Features + +* Prompt rendering support +* Resource URI fetching +* Token caching for long-lived auth +* Federation fallback if the Gateway is unreachable diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000000..eaf793bc18 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,101 @@ +# Inherit config from base +INHERIT: ./base.yml + +# Project Information +site_name: "MCP Context Forge - Model Context Protocol Gateway" +site_url: "https://pages.github.com/ibm/mcp-context-forge/" +site_description: "MCP Gateway Documentation" +site_author: "Mihai Criveti" + +# Repository Information +repo_name: 'ibm/mcp-context-forge' +repo_url: 'https://github.com/ibm/mcp-context-forge' + +# Copyright +copyright: Apache 2.0 + +# Theme settings +theme: + language: en + features: + - announce.dismiss # Button to dismiss the current temporary announcement + - content.action.edit + - content.action.feedback + # - content.action.view + - content.code.annotate # Attach arbitrary content to specific sections of code blocks by adding numeric markers + - content.code.copy # Button to allow the user to copy a code block's contents to the clipboard + # - content.tabs.link + # - navigation.expand # Expand left sidebar by default + - navigation.indexes # Attach documents to sections, useful for providing overview pages + - navigation.instant # Single page instant refresh + # - navigation.sections # Top level tabs rendered in sidebar + - navigation.tabs # Navigation tabs across top. Comment this out to have navigation on the left + - navigation.tabs.sticky # Tab always displayed + - navigation.top # Back-to-top button. Rendered centered and just below the header + - navigation.tracking # URL updated with active anchor + - search.share + - search.suggest + - toc.integrate # Integrate page TOC in left sidebar - wider page + icon: + edit: 'material/pencil-outline' + +markdown_extensions: + pymdownx.magiclink: + provider: 'github' + repo_url_shortener: true + repo_url_shorthand: false + social_url_shorthand: true + user: ibm + repo: ibm/mcp-context-forge + toc: + permalink: true + toc_depth: 3 + +plugins: + # Built-in blog plugin - see https://squidfunk.github.io/mkdocs-material/setup/setting-up-a-blog/ + # blog: + # blog_dir: "blog" + # pagination_per_page: 5 + # post_date_format: full + # post_url_date_format: yyyy/MM/dd + # Allow Tags on the pages - see https://squidfunk.github.io/mkdocs-material/setup/setting-up-tags/ + # tags: + # tags_file: tags.md + # Configure the PDF export plugin - see https://github.com/zhaoterryy/mkdocs-pdf-export-plugin + with-pdf: # This creates a single PDF document for the whole site + author: Mihai Criveti + copyright: Copyright © Apache 2.0 + cover: true + back_cover: false + # + cover_title: MCP Context Forge - MCP Gateway + cover_subtitle: MCP Gateway Documentation + cover_logo: 'theme/logo.png' + #custom_template_path: TEMPLATES PATH + # + toc_title: Contents + # heading_shift: false + toc_level: 2 + #ordered_chapter_level: 2 + #excludes_children: + # - 'release-notes/:upgrading' + # - 'release-notes/:changelog' + # + exclude_pages: + - 'blog' + #convert_iframe: + # - src: IFRAME SRC + # img: POSTER IMAGE URL + # text: ALTERNATE TEXT + # - src: ... + #two_columns_level: 3 + # + # render_js: true + #headless_chrome_path: headless-chromium + # + output_path: ./pdf/mcpgateway-docs.pdf + enabled_if_env: ENABLE_PDF_EXPORT + # + debug_html: true + #show_anchors: true + verbose: false diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000..9f0bf4a1fc --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,90 @@ +actdiag>=3.0.0 +Babel>=2.17.0 +beautifulsoup4>=4.13.4 +blockdiag>=3.0.0 +bracex>=2.5.post1 +Brotli>=1.1.0 +cairocffi>=1.7.1 +CairoSVG>=2.8.2 +certifi>=2025.4.26 +cffi>=1.17.1 +charset-normalizer>=3.4.2 +click>=8.2.1 +colorama>=0.4.6 +csscompressor>=0.9.5 +cssselect2>=0.8.0 +defusedxml>=0.7.1 +EditorConfig>=0.17.0 +flatten-json>=0.1.14 +fonttools>=4.58.0 +funcparserlib>=1.0.1 +ghp-import>=2.1.0 +gitdb>=4.0.12 +GitPython>=3.1.44 +html5lib>=1.1 +htmlmin2>=0.1.13 +idna>=3.10 +importlib-metadata>=8.7.0 +Jinja2>=3.1.6 +jsbeautifier>=1.15.4 +jsmin>=3.0.1 +libsass>=0.23.0 +Markdown>=3.8 +markdown-blockdiag>=0.8.0 +markdown-include>=0.8.1 +MarkupSafe>=3.0.2 +mergedeep>=1.3.4 +mkdocs>=1.6.1 +mkdocs-awesome-pages-plugin>=2.10.1 +mkdocs-blog-plugin>=0.25 +mkdocs-enumerate-headings-plugin>=0.6.2 +mkdocs-git-authors-plugin>=0.9.5 +mkdocs-git-revision-date-localized-plugin>=1.4.5 +mkdocs-glightbox>=0.4.0 +mkdocs-include-markdown-plugin>=7.1.5 +mkdocs-material>=9.6.14 +mkdocs-material-extensions>=1.3.1 +mkdocs-mermaid-plugin>=0.1.1 +mkdocs-mermaid2-plugin>=1.2.1 +mkdocs-minify-plugin>=0.8.0 +mkdocs-pdf-export-plugin>=0.5.10 +mkdocs-plugin-inline-svg>=0.1.0 +mkdocs-rss-plugin>=1.17.1 +mkdocs-table-reader-plugin>=3.1.0 +mkdocs-with-pdf>=0.9.3 +natsort>=8.4.0 +numpy>=2.2.6 +nwdiag>=3.0.0 +packaging>=25.0 +paginate>=0.5.7 +pandas>=2.2.3 +pathspec>=0.12.1 +pillow>=11.2.1 +platformdirs>=4.3.8 +pycparser>=2.22 +pydyf>=0.11.0 +Pygments>=2.19.1 +pymdown-extensions>=10.15 +pyparsing>=3.2.3 +pyphen>=0.17.2 +python-dateutil>=2.9.0.post0 +pytz>=2025.2 +PyYAML>=6.0.2 +pyyaml_env_tag>=1.1 +regex>=2024.11.6 +requests>=2.32.3 +seqdiag>=3.0.0 +six>=1.17.0 +smmap>=5.0.2 +soupsieve>=2.7 +tabulate>=0.9.0 +tinycss2>=1.4.0 +tzdata>=2025.2 +urllib3>=2.4.0 +watchdog>=6.0.0 +wcmatch>=10.0 +weasyprint>=65.1 +webcolors>=24.11.1 +webencodings>=0.5.1 +zipp>=3.21.0 +zopfli>=0.2.3.post1 diff --git a/docs/theme/logo.png b/docs/theme/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d444536d72beea45cb30dbaeb28c6f9963e645c8 GIT binary patch literal 6853 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 0.24.1, pip install gunicorn[eventlet] +# worker-class = "gevent" # Requires gevent >= 1.4, pip install gunicorn[gevent] +# worker_class = "tornado" # Requires tornado >= 0.2, pip install gunicorn[tornado] +# threads = 2 # A positive integer generally in the 2-4 x $(NUM_CORES) range. +# gevent + +# pidfile = '/tmp/gunicorn-pidfile' +# errorlog = '/tmp/gunicorn-errorlog' +# accesslog = '/tmp/gunicorn-accesslog' +# access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' + +# certfile = 'certs/cert.pem' +# keyfile = 'certs/key.pem' +# ca-certs = '/etc/ca_bundle.crt' + +# server hooks + + +def when_ready(server): + server.log.info("Server is ready. Spawning workers") + + +def post_fork(server, worker): + server.log.info("Worker spawned (pid: %s)", worker.pid) + + +def post_worker_init(worker): + worker.log.info("worker initialization completed") + + +def worker_int(worker): + worker.log.info("worker received INT or QUIT signal") + + +def worker_abort(worker): + worker.log.info("worker received SIGABRT signal") + + +def worker_exit(server, worker): + server.log.info("Worker exit (pid: %s)", worker.pid) + + +def child_exit(server, worker): + server.log.info("Worker child exit (pid: %s)", worker.pid) diff --git a/mcpgateway/__init__.py b/mcpgateway/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/mcpgateway/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/mcpgateway/admin.py b/mcpgateway/admin.py new file mode 100644 index 0000000000..eb8f7cfc24 --- /dev/null +++ b/mcpgateway/admin.py @@ -0,0 +1,1259 @@ +# -*- coding: utf-8 -*- +"""Admin UI Routes for MCP Gateway. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module contains all the administrative UI endpoints for the MCP Gateway. +It provides a comprehensive interface for managing servers, tools, resources, +prompts, gateways, and roots through RESTful API endpoints. The module handles +all aspects of CRUD operations for these entities, including creation, +reading, updating, deletion, and status toggling. + +All endpoints in this module require authentication, which is enforced via +the require_auth or require_basic_auth dependency. The module integrates with +various services to perform the actual business logic operations on the +underlying data. +""" + +import json +import logging +from typing import Any, Dict, List, Union + +from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse +from sqlalchemy.orm import Session + +from mcpgateway.config import settings +from mcpgateway.db import get_db +from mcpgateway.schemas import ( + GatewayCreate, + GatewayRead, + GatewayUpdate, + PromptCreate, + PromptMetrics, + PromptRead, + PromptUpdate, + ResourceCreate, + ResourceMetrics, + ResourceRead, + ResourceUpdate, + ServerCreate, + ServerMetrics, + ServerRead, + ServerUpdate, + ToolCreate, + ToolMetrics, + ToolRead, + ToolUpdate, +) +from mcpgateway.services.gateway_service import GatewayService +from mcpgateway.services.prompt_service import PromptService +from mcpgateway.services.resource_service import ResourceService +from mcpgateway.services.root_service import RootService +from mcpgateway.services.server_service import ServerNotFoundError, ServerService +from mcpgateway.services.tool_service import ( + ToolError, + ToolNameConflictError, + ToolService, +) +from mcpgateway.utils.create_jwt_token import get_jwt_token +from mcpgateway.utils.verify_credentials import require_auth, require_basic_auth + +# Initialize services +server_service = ServerService() +tool_service = ToolService() +prompt_service = PromptService() +gateway_service = GatewayService() +resource_service = ResourceService() +root_service = RootService() + +# Set up basic authentication +logger = logging.getLogger("mcpgateway") + +admin_router = APIRouter(prefix="/admin", tags=["Admin UI"]) + +#################### +# Admin UI Routes # +#################### + + +@admin_router.get("/servers", response_model=List[ServerRead]) +async def admin_list_servers( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ServerRead]: + """ + List servers for the admin UI with an option to include inactive servers. + + Args: + include_inactive (bool): Whether to include inactive servers. + db (Session): The database session dependency. + user (str): The authenticated user dependency. + + Returns: + List[ServerRead]: A list of server records. + """ + logger.debug(f"User {user} requested server list") + servers = await server_service.list_servers(db, include_inactive=include_inactive) + return [server.dict(by_alias=True) for server in servers] + + +@admin_router.get("/servers/{server_id}", response_model=ServerRead) +async def admin_get_server(server_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> ServerRead: + """ + Retrieve server details for the admin UI. + + Args: + server_id (int): The ID of the server to retrieve. + db (Session): The database session dependency. + user (str): The authenticated user dependency. + + Returns: + ServerRead: The server details. + + Raises: + HTTPException: If the server is not found. + """ + try: + logger.debug(f"User {user} requested details for server ID {server_id}") + server = await server_service.get_server(db, server_id) + return server.dict(by_alias=True) + except ServerNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + + +@admin_router.post("/servers", response_model=ServerRead) +async def admin_add_server(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """ + Add a new server via the admin UI. + + This endpoint processes form data to create a new server entry in the database. + It handles exceptions gracefully and logs any errors that occur during server + registration. + + Expects form fields: + - name (required): The name of the server + - description (optional): A description of the server's purpose + - icon (optional): URL or path to the server's icon + - associatedTools (optional, comma-separated): Tools associated with this server + - associatedResources (optional, comma-separated): Resources associated with this server + - associatedPrompts (optional, comma-separated): Prompts associated with this server + + Args: + request (Request): FastAPI request containing form data. + db (Session): Database session dependency + user (str): Authenticated user dependency + + Returns: + RedirectResponse: A redirect to the admin dashboard catalog section + """ + form = await request.form() + try: + logger.debug(f"User {user} is adding a new server with name: {form['name']}") + server = ServerCreate( + name=form["name"], + description=form.get("description"), + icon=form.get("icon"), + associated_tools=form.get("associatedTools"), + associated_resources=form.get("associatedResources"), + associated_prompts=form.get("associatedPrompts"), + ) + await server_service.register_server(db, server) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#catalog", status_code=303) + except Exception as e: + logger.error(f"Error adding server: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#catalog", status_code=303) + + +@admin_router.post("/servers/{server_id}/edit") +async def admin_edit_server( + server_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Edit an existing server via the admin UI. + + This endpoint processes form data to update an existing server's properties. + It handles exceptions gracefully and logs any errors that occur during the + update operation. + + Expects form fields: + - name (optional): The updated name of the server + - description (optional): An updated description of the server's purpose + - icon (optional): Updated URL or path to the server's icon + - associatedTools (optional, comma-separated): Updated list of tools associated with this server + - associatedResources (optional, comma-separated): Updated list of resources associated with this server + - associatedPrompts (optional, comma-separated): Updated list of prompts associated with this server + + Args: + server_id (int): The ID of the server to edit + request (Request): FastAPI request containing form data + db (Session): Database session dependency + user (str): Authenticated user dependency + + Returns: + RedirectResponse: A redirect to the admin dashboard catalog section with a status code of 303 + """ + form = await request.form() + try: + logger.debug(f"User {user} is editing server ID {server_id} with name: {form.get('name')}") + server = ServerUpdate( + name=form.get("name"), + description=form.get("description"), + icon=form.get("icon"), + associated_tools=form.get("associatedTools"), + associated_resources=form.get("associatedResources"), + associated_prompts=form.get("associatedPrompts"), + ) + await server_service.update_server(db, server_id, server) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#catalog", status_code=303) + except Exception as e: + logger.error(f"Error editing server: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#catalog", status_code=303) + + +@admin_router.post("/servers/{server_id}/toggle") +async def admin_toggle_server( + server_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Toggle a server's active status via the admin UI. + + This endpoint processes a form request to activate or deactivate a server. + It expects a form field 'activate' with value "true" to activate the server + or "false" to deactivate it. The endpoint handles exceptions gracefully and + logs any errors that might occur during the status toggle operation. + + Args: + server_id (int): The ID of the server whose status to toggle. + request (Request): FastAPI request containing form data with the 'activate' field. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect to the admin dashboard catalog section with a + status code of 303 (See Other). + """ + form = await request.form() + logger.debug(f"User {user} is toggling server ID {server_id} with activate: {form.get('activate')}") + activate = form.get("activate", "true").lower() == "true" + try: + await server_service.toggle_server_status(db, server_id, activate) + except Exception as e: + logger.error(f"Error toggling server status: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#catalog", status_code=303) + + +@admin_router.post("/servers/{server_id}/delete") +async def admin_delete_server(server_id: int, request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """ + Delete a server via the admin UI. + + This endpoint removes a server from the database by its ID. It handles exceptions + gracefully and logs any errors that occur during the deletion process. + + Args: + server_id (int): The ID of the server to delete + request (Request): FastAPI request object (not used but required by route signature). + db (Session): Database session dependency + user (str): Authenticated user dependency + + Returns: + RedirectResponse: A redirect to the admin dashboard catalog section with a + status code of 303 (See Other) + """ + try: + logger.debug(f"User {user} is deleting server ID {server_id}") + await server_service.delete_server(db, server_id) + except Exception as e: + logger.error(f"Error deleting server: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#catalog", status_code=303) + + +@admin_router.get("/resources", response_model=List[ResourceRead]) +async def admin_list_resources( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ResourceRead]: + """ + List resources for the admin UI with an option to include inactive resources. + + This endpoint retrieves a list of resources from the database, optionally including + those that are inactive. The inactive filter is useful for administrators who need + to view or manage resources that have been deactivated but not deleted. + + Args: + include_inactive (bool): Whether to include inactive resources in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[ResourceRead]: A list of resource records formatted with by_alias=True. + """ + logger.debug(f"User {user} requested resource list") + resources = await resource_service.list_resources(db, include_inactive=include_inactive) + return [resource.dict(by_alias=True) for resource in resources] + + +@admin_router.get("/prompts", response_model=List[PromptRead]) +async def admin_list_prompts( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[PromptRead]: + """ + List prompts for the admin UI with an option to include inactive prompts. + + This endpoint retrieves a list of prompts from the database, optionally including + those that are inactive. The inactive filter helps administrators see and manage + prompts that have been deactivated but not deleted from the system. + + Args: + include_inactive (bool): Whether to include inactive prompts in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[PromptRead]: A list of prompt records formatted with by_alias=True. + """ + logger.debug(f"User {user} requested prompt list") + prompts = await prompt_service.list_prompts(db, include_inactive=include_inactive) + return [prompt.dict(by_alias=True) for prompt in prompts] + + +@admin_router.get("/gateways", response_model=List[GatewayRead]) +async def admin_list_gateways( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[GatewayRead]: + """ + List gateways for the admin UI with an option to include inactive gateways. + + This endpoint retrieves a list of gateways from the database, optionally + including those that are inactive. The inactive filter allows administrators + to view and manage gateways that have been deactivated but not deleted. + + Args: + include_inactive (bool): Whether to include inactive gateways in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[GatewayRead]: A list of gateway records formatted with by_alias=True. + """ + logger.debug(f"User {user} requested gateway list") + gateways = await gateway_service.list_gateways(db, include_inactive=include_inactive) + return [gateway.dict(by_alias=True) for gateway in gateways] + + +@admin_router.post("/gateways/{gateway_id}/toggle") +async def admin_toggle_gateway( + gateway_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Toggle the active status of a gateway via the admin UI. + + This endpoint allows an admin to toggle the active status of a gateway. + It expects a form field 'activate' with a value of "true" or "false" to + determine the new status of the gateway. + + Args: + gateway_id (int): The ID of the gateway to toggle. + request (Request): The FastAPI request object containing form data. + db (Session): The database session dependency. + user (str): The authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the admin dashboard with a + status code of 303 (See Other). + """ + logger.debug(f"User {user} is toggling gateway ID {gateway_id}") + form = await request.form() + activate = form.get("activate", "true").lower() == "true" + try: + await gateway_service.toggle_gateway_status(db, gateway_id, activate) + except Exception as e: + logger.error(f"Error toggling gateway status: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#gateways", status_code=303) + + +@admin_router.get("/", name="admin_home", response_class=HTMLResponse) +async def admin_ui( + request: Request, + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_basic_auth), + jwt_token: str = Depends(get_jwt_token), +) -> HTMLResponse: + """ + Render the admin dashboard HTML page. + + This endpoint serves as the main entry point to the admin UI. It fetches data for + servers, tools, resources, prompts, gateways, and roots from their respective + services, then renders the admin dashboard template with this data. + + The endpoint also sets a JWT token as a cookie for authentication in subsequent + requests. This token is HTTP-only for security reasons. + + Args: + request (Request): FastAPI request object. + include_inactive (bool): Whether to include inactive items in all listings. + db (Session): Database session dependency. + user (str): Authenticated user from basic auth dependency. + jwt_token (str): JWT token for authentication. + + Returns: + HTMLResponse: Rendered HTML template for the admin dashboard. + """ + logger.debug(f"User {user} accessed the admin UI") + servers = [server.dict(by_alias=True) for server in await server_service.list_servers(db, include_inactive=include_inactive)] + tools = [tool.dict(by_alias=True) for tool in await tool_service.list_tools(db, include_inactive=include_inactive)] + resources = [resource.dict(by_alias=True) for resource in await resource_service.list_resources(db, include_inactive=include_inactive)] + prompts = [prompt.dict(by_alias=True) for prompt in await prompt_service.list_prompts(db, include_inactive=include_inactive)] + gateways = [gateway.dict(by_alias=True) for gateway in await gateway_service.list_gateways(db, include_inactive=include_inactive)] + roots = [root.dict(by_alias=True) for root in await root_service.list_roots()] + root_path = settings.app_root_path + response = request.app.state.templates.TemplateResponse( + "admin.html", + { + "request": request, + "servers": servers, + "tools": tools, + "resources": resources, + "prompts": prompts, + "gateways": gateways, + "roots": roots, + "include_inactive": include_inactive, + "root_path": root_path, + }, + ) + + response.set_cookie(key="jwt_token", value=jwt_token, httponly=True, secure=False, samesite="Strict") # JavaScript CAN’T read it # only over HTTPS # or "Lax" per your needs + return response + + +@admin_router.get("/tools", response_model=List[ToolRead]) +async def admin_list_tools( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ToolRead]: + """ + List tools for the admin UI with an option to include inactive tools. + + This endpoint retrieves a list of tools from the database, optionally including + those that are inactive. The inactive filter helps administrators manage tools + that have been deactivated but not deleted from the system. + + Args: + include_inactive (bool): Whether to include inactive tools in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[ToolRead]: A list of tool records formatted with by_alias=True. + """ + logger.debug(f"User {user} requested tool list") + tools = await tool_service.list_tools(db, include_inactive=include_inactive) + return [tool.dict(by_alias=True) for tool in tools] + + +@admin_router.get("/tools/{tool_id}", response_model=ToolRead) +async def admin_get_tool(tool_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> ToolRead: + """ + Retrieve specific tool details for the admin UI. + + This endpoint fetches the details of a specific tool from the database + by its ID. It provides access to all information about the tool for + viewing and management purposes. + + Args: + tool_id (int): The ID of the tool to retrieve. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + ToolRead: The tool details formatted with by_alias=True. + """ + logger.debug(f"User {user} requested details for tool ID {tool_id}") + tool = await tool_service.get_tool(db, tool_id) + return tool.dict(by_alias=True) + + +@admin_router.post("/tools/") +@admin_router.post("/tools") +async def admin_add_tool( + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> JSONResponse: + """ + Add a tool via the admin UI with error handling. + + Expects form fields: + - name + - url + - description (optional) + - requestType (mapped to request_type; defaults to "SSE") + - integrationType (mapped to integration_type; defaults to "MCP") + - headers (JSON string) + - input_schema (JSON string) + - jsonpath_filter (optional) + - auth_type (optional) + - auth_username (optional) + - auth_password (optional) + - auth_token (optional) + - auth_header_key (optional) + - auth_header_value (optional) + + Logs the raw form data and assembled tool_data for debugging. + + Args: + request (Request): the FastAPI request object containing the form data. + db (Session): the SQLAlchemy database session. + user (str): identifier of the authenticated user. + + Returns: + JSONResponse: a JSON response with `{"message": ..., "success": ...}` and an appropriate HTTP status code. + """ + logger.debug(f"User {user} is adding a new tool") + form = await request.form() + logger.info(f"Received form data: {dict(form)}") + + tool_data = { + "name": form["name"], + "url": form["url"], + "description": form.get("description"), + "request_type": form.get("requestType", "SSE"), + "integration_type": form.get("integrationType", "MCP"), + "headers": json.loads(form.get("headers") or "{}"), + "input_schema": json.loads(form.get("input_schema") or "{}"), + "jsonpath_filter": form.get("jsonpath_filter", ""), + "auth_type": form.get("auth_type", ""), + "auth_username": form.get("auth_username", ""), + "auth_password": form.get("auth_password", ""), + "auth_token": form.get("auth_token", ""), + "auth_header_key": form.get("auth_header_key", ""), + "auth_header_value": form.get("auth_header_value", ""), + } + logger.info(f"Tool data built: {tool_data}") + try: + tool = ToolCreate(**tool_data) + logger.info(f"Validated tool data: {tool.dict()}") + await tool_service.register_tool(db, tool) + return JSONResponse( + content={"message": "Tool registered successfully!", "success": True}, + status_code=200, + ) + except ToolNameConflictError as e: + logger.error(f"ToolNameConflictError: {str(e)}") + return JSONResponse(content={"message": str(e), "success": False}, status_code=400) + except Exception as e: + logger.error(f"Error in admin_add_tool: {str(e)}") + return JSONResponse(content={"message": str(e), "success": False}, status_code=500) + + +@admin_router.post("/tools/{tool_id}/edit/") +@admin_router.post("/tools/{tool_id}/edit") +async def admin_edit_tool( + tool_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Edit a tool via the admin UI. + + Expects form fields: + - name + - url + - description (optional) + - requestType (to be mapped to request_type) + - integrationType (to be mapped to integration_type) + - headers (as a JSON string) + - input_schema (as a JSON string) + - jsonpathFilter (optional) + - auth_type (optional, string: "basic", "bearer", or empty) + - auth_username (optional, for basic auth) + - auth_password (optional, for basic auth) + - auth_token (optional, for bearer auth) + - auth_header_key (optional, for headers auth) + - auth_header_value (optional, for headers auth) + + Assembles the tool_data dictionary by remapping form keys into the + snake-case keys expected by the schemas. + + Args: + tool_id (int): The ID of the tool to edit. + request (Request): FastAPI request containing form data. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the tools section of the admin + dashboard with a status code of 303 (See Other), or a JSON response with + an error message if the update fails. + """ + logger.debug(f"User {user} is editing tool ID {tool_id}") + form = await request.form() + tool_data = { + "name": form["name"], + "url": form["url"], + "description": form.get("description"), + "request_type": form.get("requestType", "SSE"), + "integration_type": form.get("integrationType", "MCP"), + "headers": json.loads(form.get("headers") or "{}"), + "input_schema": json.loads(form.get("input_schema") or "{}"), + "jsonpath_filter": form.get("jsonpathFilter", ""), + "auth_type": form.get("auth_type", ""), + "auth_username": form.get("auth_username", ""), + "auth_password": form.get("auth_password", ""), + "auth_token": form.get("auth_token", ""), + "auth_header_key": form.get("auth_header_key", ""), + "auth_header_value": form.get("auth_header_value", ""), + } + logger.info(f"Tool update data built: {tool_data}") + tool = ToolUpdate(**tool_data) + try: + await tool_service.update_tool(db, tool_id, tool) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#tools", status_code=303) + except ToolNameConflictError as e: + return JSONResponse(content={"message": str(e), "success": False}, status_code=400) + except ToolError as e: + return JSONResponse(content={"message": str(e), "success": False}, status_code=500) + + +@admin_router.post("/tools/{tool_id}/delete") +async def admin_delete_tool(tool_id: int, request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """ + Delete a tool via the admin UI. + + This endpoint permanently removes a tool from the database using its ID. + It is irreversible and should be used with caution. The operation is logged, + and the user must be authenticated to access this route. + + Args: + tool_id (int): The ID of the tool to delete. + request (Request): FastAPI request object (not used directly, but required by route signature). + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the tools section of the admin + dashboard with a status code of 303 (See Other). + """ + logger.debug(f"User {user} is deleting tool ID {tool_id}") + await tool_service.delete_tool(db, tool_id) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#tools", status_code=303) + + +@admin_router.post("/tools/{tool_id}/toggle") +async def admin_toggle_tool( + tool_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Toggle a tool's active status via the admin UI. + + This endpoint processes a form request to activate or deactivate a tool. + It expects a form field 'activate' with value "true" to activate the tool + or "false" to deactivate it. The endpoint handles exceptions gracefully and + logs any errors that might occur during the status toggle operation. + + Args: + tool_id (int): The ID of the tool whose status to toggle. + request (Request): FastAPI request containing form data with the 'activate' field. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect to the admin dashboard tools section with a + status code of 303 (See Other). + """ + logger.debug(f"User {user} is toggling tool ID {tool_id}") + form = await request.form() + activate = form.get("activate", "true").lower() == "true" + try: + await tool_service.toggle_tool_status(db, tool_id, activate) + except Exception as e: + logger.error(f"Error toggling tool status: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#tools", status_code=303) + + +@admin_router.get("/gateways/{gateway_id}", response_model=GatewayRead) +async def admin_get_gateway(gateway_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> GatewayRead: + """Get gateway details for the admin UI. + + Args: + gateway_id: Gateway ID. + db: Database session. + user: Authenticated user. + + Returns: + Gateway details. + """ + logger.debug(f"User {user} requested details for gateway ID {gateway_id}") + gateway = await gateway_service.get_gateway(db, gateway_id) + return gateway.dict(by_alias=True) + + +@admin_router.post("/gateways") +async def admin_add_gateway(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """Add a gateway via the admin UI. + + Expects form fields: + - name + - url + - description (optional) + + Args: + request: FastAPI request containing form data. + db: Database session. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is adding a new gateway") + form = await request.form() + gateway = GatewayCreate( + name=form["name"], + url=form["url"], + description=form.get("description"), + auth_type=form.get("auth_type", ""), + auth_username=form.get("auth_username", ""), + auth_password=form.get("auth_password", ""), + auth_token=form.get("auth_token", ""), + auth_header_key=form.get("auth_header_key", ""), + auth_header_value=form.get("auth_header_value", ""), + ) + await gateway_service.register_gateway(db, gateway) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#gateways", status_code=303) + + +@admin_router.post("/gateways/{gateway_id}/edit") +async def admin_edit_gateway( + gateway_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """Edit a gateway via the admin UI. + + Expects form fields: + - name + - url + - description (optional) + + Args: + gateway_id: Gateway ID. + request: FastAPI request containing form data. + db: Database session. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is editing gateway ID {gateway_id}") + form = await request.form() + gateway = GatewayUpdate( + name=form["name"], + url=form["url"], + description=form.get("description"), + auth_type=form.get("auth_type", None), + auth_username=form.get("auth_username", None), + auth_password=form.get("auth_password", None), + auth_token=form.get("auth_token", None), + auth_header_key=form.get("auth_header_key", None), + auth_header_value=form.get("auth_header_value", None), + ) + await gateway_service.update_gateway(db, gateway_id, gateway) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#gateways", status_code=303) + + +@admin_router.post("/gateways/{gateway_id}/delete") +async def admin_delete_gateway(gateway_id: int, request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """ + Delete a gateway via the admin UI. + + This endpoint removes a gateway from the database by its ID. The deletion is + permanent and cannot be undone. It requires authentication and logs the + operation for auditing purposes. + + Args: + gateway_id (int): The ID of the gateway to delete. + request (Request): FastAPI request object (not used directly but required by the route signature). + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the gateways section of the admin + dashboard with a status code of 303 (See Other). + """ + logger.debug(f"User {user} is deleting gateway ID {gateway_id}") + await gateway_service.delete_gateway(db, gateway_id) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#gateways", status_code=303) + + +@admin_router.get("/resources/{uri:path}") +async def admin_get_resource(uri: str, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, Any]: + """Get resource details for the admin UI. + + Args: + uri: Resource URI. + db: Database session. + user: Authenticated user. + + Returns: + A dictionary containing resource details and its content. + """ + logger.debug(f"User {user} requested details for resource URI {uri}") + resource = await resource_service.get_resource_by_uri(db, uri) + content = await resource_service.read_resource(db, uri) + return {"resource": resource.dict(by_alias=True), "content": content} + + +@admin_router.post("/resources") +async def admin_add_resource(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """Add a resource via the admin UI. + + Expects form fields: + - uri + - name + - description (optional) + - mime_type (optional) + - content + + Args: + request: FastAPI request containing form data. + db: Database session. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is adding a new resource") + form = await request.form() + resource = ResourceCreate( + uri=form["uri"], + name=form["name"], + description=form.get("description"), + mime_type=form.get("mimeType"), + template=form.get("template"), # defaults to None if not provided + content=form["content"], + ) + await resource_service.register_resource(db, resource) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#resources", status_code=303) + + +@admin_router.post("/resources/{uri:path}/edit") +async def admin_edit_resource( + uri: str, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """Edit a resource via the admin UI. + + Expects form fields: + - name + - description (optional) + - mime_type (optional) + - content + + Args: + uri: Resource URI. + request: FastAPI request containing form data. + db: Database session. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is editing resource URI {uri}") + form = await request.form() + resource = ResourceUpdate( + name=form["name"], + description=form.get("description"), + mime_type=form.get("mimeType"), + content=form["content"], + ) + await resource_service.update_resource(db, uri, resource) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#resources", status_code=303) + + +@admin_router.post("/resources/{uri:path}/delete") +async def admin_delete_resource(uri: str, request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """ + Delete a resource via the admin UI. + + This endpoint permanently removes a resource from the database using its URI. + The operation is irreversible and should be used with caution. It requires + user authentication and logs the deletion attempt. + + Args: + uri (str): The URI of the resource to delete. + request (Request): FastAPI request object (not used directly but required by the route signature). + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the resources section of the admin + dashboard with a status code of 303 (See Other). + """ + logger.debug(f"User {user} is deleting resource URI {uri}") + await resource_service.delete_resource(db, uri) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#resources", status_code=303) + + +@admin_router.post("/resources/{resource_id}/toggle") +async def admin_toggle_resource( + resource_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Toggle a resource's active status via the admin UI. + + This endpoint processes a form request to activate or deactivate a resource. + It expects a form field 'activate' with value "true" to activate the resource + or "false" to deactivate it. The endpoint handles exceptions gracefully and + logs any errors that might occur during the status toggle operation. + + Args: + resource_id (int): The ID of the resource whose status to toggle. + request (Request): FastAPI request containing form data with the 'activate' field. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect to the admin dashboard resources section with a + status code of 303 (See Other). + """ + logger.debug(f"User {user} is toggling resource ID {resource_id}") + form = await request.form() + activate = form.get("activate", "true").lower() == "true" + try: + await resource_service.toggle_resource_status(db, resource_id, activate) + except Exception as e: + logger.error(f"Error toggling resource status: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#resources", status_code=303) + + +@admin_router.get("/prompts/{name}") +async def admin_get_prompt(name: str, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, Any]: + """Get prompt details for the admin UI. + + Args: + name: Prompt name. + db: Database session. + user: Authenticated user. + + Returns: + A dictionary with prompt details. + """ + logger.debug(f"User {user} requested details for prompt name {name}") + prompt_details = await prompt_service.get_prompt_details(db, name) + + prompt = PromptRead.model_validate(prompt_details) + return prompt.dict(by_alias=True) + + +@admin_router.post("/prompts") +async def admin_add_prompt(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """Add a prompt via the admin UI. + + Expects form fields: + - name + - description (optional) + - template + - arguments (as a JSON string representing a list) + + Args: + request: FastAPI request containing form data. + db: Database session. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is adding a new prompt") + form = await request.form() + args_json = form.get("arguments") or "[]" + arguments = json.loads(args_json) + prompt = PromptCreate( + name=form["name"], + description=form.get("description"), + template=form["template"], + arguments=arguments, + ) + await prompt_service.register_prompt(db, prompt) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#prompts", status_code=303) + + +@admin_router.post("/prompts/{name}/edit") +async def admin_edit_prompt( + name: str, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """Edit a prompt via the admin UI. + + Expects form fields: + - name + - description (optional) + - template + - arguments (as a JSON string representing a list) + + Args: + name: Prompt name. + request: FastAPI request containing form data. + db: Database session. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is editing prompt name {name}") + form = await request.form() + args_json = form.get("arguments") or "[]" + arguments = json.loads(args_json) + prompt = PromptUpdate( + name=form["name"], + description=form.get("description"), + template=form["template"], + arguments=arguments, + ) + await prompt_service.update_prompt(db, name, prompt) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#prompts", status_code=303) + + +@admin_router.post("/prompts/{name}/delete") +async def admin_delete_prompt(name: str, request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> RedirectResponse: + """ + Delete a prompt via the admin UI. + + This endpoint permanently deletes a prompt from the database using its name. + Deletion is irreversible and requires authentication. All actions are logged + for administrative auditing. + + Args: + name (str): The name of the prompt to delete. + request (Request): FastAPI request object (not used directly but required by the route signature). + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the prompts section of the admin + dashboard with a status code of 303 (See Other). + """ + logger.debug(f"User {user} is deleting prompt name {name}") + await prompt_service.delete_prompt(db, name) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#prompts", status_code=303) + + +@admin_router.post("/prompts/{prompt_id}/toggle") +async def admin_toggle_prompt( + prompt_id: int, + request: Request, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> RedirectResponse: + """ + Toggle a prompt's active status via the admin UI. + + This endpoint processes a form request to activate or deactivate a prompt. + It expects a form field 'activate' with value "true" to activate the prompt + or "false" to deactivate it. The endpoint handles exceptions gracefully and + logs any errors that might occur during the status toggle operation. + + Args: + prompt_id (int): The ID of the prompt whose status to toggle. + request (Request): FastAPI request containing form data with the 'activate' field. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect to the admin dashboard prompts section with a + status code of 303 (See Other). + """ + logger.debug(f"User {user} is toggling prompt ID {prompt_id}") + form = await request.form() + activate = form.get("activate", "true").lower() == "true" + try: + await prompt_service.toggle_prompt_status(db, prompt_id, activate) + except Exception as e: + logger.error(f"Error toggling prompt status: {e}") + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#prompts", status_code=303) + + +@admin_router.post("/roots") +async def admin_add_root(request: Request, user: str = Depends(require_auth)) -> RedirectResponse: + """Add a new root via the admin UI. + + Expects form fields: + - path + - name (optional) + + Args: + request: FastAPI request containing form data. + user: Authenticated user. + + Returns: + A redirect response to the admin dashboard. + """ + logger.debug(f"User {user} is adding a new root") + form = await request.form() + uri = form["uri"] + name = form.get("name") + await root_service.add_root(uri, name) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#roots", status_code=303) + + +@admin_router.post("/roots/{uri:path}/delete") +async def admin_delete_root(uri: str, request: Request, user: str = Depends(require_auth)) -> RedirectResponse: + """ + Delete a root via the admin UI. + + This endpoint removes a registered root URI from the system. The deletion is + permanent and cannot be undone. It requires authentication and logs the + operation for audit purposes. + + Args: + uri (str): The URI of the root to delete. + request (Request): FastAPI request object (not used directly but required by the route signature). + user (str): Authenticated user dependency. + + Returns: + RedirectResponse: A redirect response to the roots section of the admin + dashboard with a status code of 303 (See Other). + """ + logger.debug(f"User {user} is deleting root URI {uri}") + await root_service.remove_root(uri) + + root_path = request.scope.get("root_path", "") + return RedirectResponse(f"{root_path}/admin#roots", status_code=303) + + +# Metrics +MetricsDict = Dict[str, Union[ToolMetrics, ResourceMetrics, ServerMetrics, PromptMetrics]] + + +@admin_router.get("/metrics", response_model=MetricsDict) +async def admin_get_metrics( + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> MetricsDict: + """ + Retrieve aggregate metrics for all entity types via the admin UI. + + This endpoint collects and returns usage metrics for tools, resources, servers, + and prompts. The metrics are retrieved by calling the aggregate_metrics method + on each respective service, which compiles statistics about usage patterns, + success rates, and other relevant metrics for administrative monitoring + and analysis purposes. + + Args: + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + MetricsDict: A dictionary containing the aggregated metrics for tools, + resources, servers, and prompts. Each value is a Pydantic model instance + specific to the entity type. + """ + logger.debug(f"User {user} requested aggregate metrics") + tool_metrics = await tool_service.aggregate_metrics(db) + resource_metrics = await resource_service.aggregate_metrics(db) + server_metrics = await server_service.aggregate_metrics(db) + prompt_metrics = await prompt_service.aggregate_metrics(db) + + # Return actual Pydantic model instances + return { + "tools": tool_metrics, + "resources": resource_metrics, + "servers": server_metrics, + "prompts": prompt_metrics, + } + + +@admin_router.post("/metrics/reset", response_model=Dict[str, object]) +async def admin_reset_metrics(db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, object]: + """ + Reset all metrics for tools, resources, servers, and prompts. + Each service must implement its own reset_metrics method. + + Args: + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + Dict[str, object]: A dictionary containing a success message and status. + """ + logger.debug(f"User {user} requested to reset all metrics") + await tool_service.reset_metrics(db) + await resource_service.reset_metrics(db) + await server_service.reset_metrics(db) + await prompt_service.reset_metrics(db) + return {"message": "All metrics reset successfully", "success": True} diff --git a/mcpgateway/cache/__init__.py b/mcpgateway/cache/__init__.py new file mode 100644 index 0000000000..be45afa912 --- /dev/null +++ b/mcpgateway/cache/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +"""Cache Package. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Provides caching components for the MCP Gateway including: +- Resource content caching +""" + +from mcpgateway.cache.resource_cache import ResourceCache +from mcpgateway.cache.session_registry import SessionRegistry + +__all__ = ["ResourceCache", "SessionRegistry"] diff --git a/mcpgateway/cache/resource_cache.py b/mcpgateway/cache/resource_cache.py new file mode 100644 index 0000000000..50278b1d35 --- /dev/null +++ b/mcpgateway/cache/resource_cache.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +"""Resource Cache Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements a simple in-memory cache with TTL expiration for caching +resource content in the MCP Gateway. Features: +- TTL-based expiration +- Maximum size limit with LRU eviction +- Thread-safe operations +""" + +import asyncio +import logging +import time +from dataclasses import dataclass +from typing import Any, Dict, Optional + +logger = logging.getLogger(__name__) + + +@dataclass +class CacheEntry: + """Cache entry with expiration.""" + + value: Any + expires_at: float + last_access: float + + +class ResourceCache: + """Resource content cache with TTL expiration. + + Attributes: + max_size: Maximum number of entries + ttl: Time-to-live in seconds + _cache: Cache storage + _lock: Async lock for thread safety + """ + + def __init__(self, max_size: int = 1000, ttl: int = 3600): + """Initialize cache. + + Args: + max_size: Maximum number of entries + ttl: Time-to-live in seconds + """ + self.max_size = max_size + self.ttl = ttl + self._cache: Dict[str, CacheEntry] = {} + self._lock = asyncio.Lock() + + async def initialize(self) -> None: + """Initialize cache service.""" + logger.info("Initializing resource cache") + # Start cleanup task + asyncio.create_task(self._cleanup_loop()) + + async def shutdown(self) -> None: + """Shutdown cache service.""" + logger.info("Shutting down resource cache") + self.clear() + + def get(self, key: str) -> Optional[Any]: + """Get value from cache. + + Args: + key: Cache key + + Returns: + Cached value or None if not found/expired + """ + if key not in self._cache: + return None + + entry = self._cache[key] + now = time.time() + + # Check expiration + if now > entry.expires_at: + del self._cache[key] + return None + + # Update access time + entry.last_access = now + return entry.value + + def set(self, key: str, value: Any) -> None: + """Set value in cache. + + Args: + key: Cache key + value: Value to cache + """ + now = time.time() + + # Check size limit + if len(self._cache) >= self.max_size: + # Remove least recently used + lru_key = min(self._cache.keys(), key=lambda k: self._cache[k].last_access) + del self._cache[lru_key] + + # Add new entry + self._cache[key] = CacheEntry(value=value, expires_at=now + self.ttl, last_access=now) + + def delete(self, key: str) -> None: + """Delete value from cache. + + Args: + key: Cache key to delete + """ + self._cache.pop(key, None) + + def clear(self) -> None: + """Clear all cached entries.""" + self._cache.clear() + + async def _cleanup_loop(self) -> None: + """Background task to clean expired entries.""" + while True: + try: + async with self._lock: + now = time.time() + expired = [key for key, entry in self._cache.items() if now > entry.expires_at] + for key in expired: + del self._cache[key] + + if expired: + logger.debug(f"Cleaned {len(expired)} expired cache entries") + + except Exception as e: + logger.error(f"Cache cleanup error: {e}") + + await asyncio.sleep(60) # Run every minute diff --git a/mcpgateway/cache/session_registry.py b/mcpgateway/cache/session_registry.py new file mode 100644 index 0000000000..41c721444d --- /dev/null +++ b/mcpgateway/cache/session_registry.py @@ -0,0 +1,760 @@ +# -*- coding: utf-8 -*- +"""Session Registry with optional distributed state. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module provides a registry for SSE sessions with support for distributed deployment +using Redis or SQLAlchemy as optional backends for shared state between workers. +""" + +import asyncio +import json +import logging +import time +from typing import Any, Dict, Optional + +import httpx +from fastapi import HTTPException, status + +from mcpgateway.config import settings +from mcpgateway.db import SessionMessageRecord, SessionRecord, get_db +from mcpgateway.services import PromptService, ResourceService, ToolService +from mcpgateway.transports import SSETransport +from mcpgateway.types import Implementation, InitializeResult, ServerCapabilities + +logger = logging.getLogger(__name__) + +tool_service = ToolService() +resource_service = ResourceService() +prompt_service = PromptService() + +try: + from redis.asyncio import Redis + + REDIS_AVAILABLE = True +except ImportError: + REDIS_AVAILABLE = False + +try: + from sqlalchemy import func + + SQLALCHEMY_AVAILABLE = True +except ImportError: + SQLALCHEMY_AVAILABLE = False + + +class SessionBackend: + """Session backend related fields""" + + def __init__( + self, + backend: str = "memory", + redis_url: Optional[str] = None, + database_url: Optional[str] = None, + session_ttl: int = 3600, # 1 hour + message_ttl: int = 600, # 10 min + ): + """Initialize session registry. + + Args: + backend: "memory", "redis", "database", or "none" + redis_url: Redis connection URL (required for redis backend) + database_url: Database connection URL (required for database backend) + session_ttl: Session time-to-live in seconds + message_ttl: Message time-to-live in seconds + + Raises: + ValueError: If backend is invalid or required URL is missing + """ + + self._backend = backend.lower() + self._session_ttl = session_ttl + self._message_ttl = message_ttl + + # Set up backend-specific components + if self._backend == "memory": + # Nothing special needed for memory backend + self._session_message = None + + elif self._backend == "none": + # No session tracking - this is just a dummy registry + logger.info("Session registry initialized with 'none' backend - session tracking disabled") + + elif self._backend == "redis": + if not REDIS_AVAILABLE: + raise ValueError("Redis backend requested but redis package not installed") + if not redis_url: + raise ValueError("Redis backend requires redis_url") + + self._redis = Redis.from_url(redis_url) + self._pubsub = self._redis.pubsub() + self._pubsub.subscribe("mcp_session_events") + + elif self._backend == "database": + if not SQLALCHEMY_AVAILABLE: + raise ValueError("Database backend requested but SQLAlchemy not installed") + if not database_url: + raise ValueError("Database backend requires database_url") + else: + raise ValueError(f"Invalid backend: {backend}") + + +class SessionRegistry(SessionBackend): + """Registry for SSE sessions with optional distributed state. + + Supports three backend modes: + - memory: In-memory storage (default, no dependencies) + - redis: Redis-backed shared storage + - database: SQLAlchemy-backed shared storage + + In distributed mode (redis/database), session existence is tracked in the shared + backend while transports themselves remain local to each worker process. + """ + + def __init__( + self, + backend: str = "memory", + redis_url: Optional[str] = None, + database_url: Optional[str] = None, + session_ttl: int = 3600, # 1 hour + message_ttl: int = 600, # 10 min + ): + """Initialize session registry. + + Args: + backend: "memory", "redis", "database", or "none" + redis_url: Redis connection URL (required for redis backend) + database_url: Database connection URL (required for database backend) + session_ttl: Session time-to-live in seconds + message_ttl: Message time-to-live in seconds + + Raises: + ValueError: If backend is invalid or required URL is missing + """ + super().__init__(backend=backend, redis_url=redis_url, database_url=database_url, session_ttl=session_ttl, message_ttl=message_ttl) + self._sessions: Dict[str, Any] = {} # Local transport cache + self._lock = asyncio.Lock() + self._cleanup_task = None + + async def initialize(self) -> None: + """Initialize the registry with async setup. + + Call this during application startup. + """ + logger.info(f"Initializing session registry with backend: {self._backend}") + + if self._backend == "database": + # Start database cleanup task + self._cleanup_task = asyncio.create_task(self._db_cleanup_task()) + logger.info("Database cleanup task started") + + elif self._backend == "none": + # Nothing to initialize for none backend + pass + + # Memory backend needs session cleanup + elif self._backend == "memory": + self._cleanup_task = asyncio.create_task(self._memory_cleanup_task()) + logger.info("Memory cleanup task started") + + async def shutdown(self) -> None: + """Shutdown the registry. + + Call this during application shutdown. + """ + logger.info("Shutting down session registry") + + # Cancel cleanup task + if self._cleanup_task: + self._cleanup_task.cancel() + try: + await self._cleanup_task + except asyncio.CancelledError: + pass + + # Close Redis connections + if self._backend == "redis": + try: + self._pubsub.close() + self._redis.close() + except Exception as e: + logger.error(f"Error closing Redis connection: {e}") + + async def add_session(self, session_id: str, transport: SSETransport) -> None: + """Add a session to the registry. + + Args: + session_id: Unique session identifier + transport: Transport session + """ + # Skip for none backend + if self._backend == "none": + return + + async with self._lock: + self._sessions[session_id] = transport + + if self._backend == "redis": + # Store session marker in Redis + try: + await self._redis.setex(f"mcp:session:{session_id}", self._session_ttl, "1") + # Publish event to notify other workers + await self._redis.publish("mcp_session_events", json.dumps({"type": "add", "session_id": session_id, "timestamp": time.time()})) + except Exception as e: + logger.error(f"Redis error adding session {session_id}: {e}") + + elif self._backend == "database": + # Store session in database + try: + + def _db_add(): + db_session = next(get_db()) + try: + session_record = SessionRecord(session_id=session_id) + db_session.add(session_record) + db_session.commit() + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + await asyncio.to_thread(_db_add) + except Exception as e: + logger.error(f"Database error adding session {session_id}: {e}") + + logger.info(f"Added session: {session_id}") + + async def get_session(self, session_id: str) -> Any: + """Get session by ID. + + Args: + session_id: Session identifier + + Returns: + Transport object or None if not found + """ + # Skip for none backend + if self._backend == "none": + return None + + # First check local cache + async with self._lock: + transport = self._sessions.get(session_id) + if transport: + logger.info(f"Session {session_id} exists in local cache") + return transport + + # If not in local cache, check if it exists in shared backend + if self._backend == "redis": + try: + exists = await self._redis.exists(f"mcp:session:{session_id}") + session_exists = bool(exists) + if session_exists: + logger.info(f"Session {session_id} exists in Redis but not in local cache") + return None # We don't have the transport locally + except Exception as e: + logger.error(f"Redis error checking session {session_id}: {e}") + return None + + elif self._backend == "database": + try: + + def _db_check(): + db_session = next(get_db()) + try: + record = db_session.query(SessionRecord).filter(SessionRecord.session_id == session_id).first() + return record is not None + finally: + db_session.close() + + exists = await asyncio.to_thread(_db_check) + if exists: + logger.info(f"Session {session_id} exists in database but not in local cache") + return None + except Exception as e: + logger.error(f"Database error checking session {session_id}: {e}") + return None + + return None + + async def remove_session(self, session_id: str) -> None: + """Remove a session from the registry. + + Args: + session_id: Session identifier + """ + # Skip for none backend + if self._backend == "none": + return + + # Clean up local transport + transport = None + async with self._lock: + if session_id in self._sessions: + transport = self._sessions.pop(session_id) + + # Disconnect transport if found + if transport: + try: + await transport.disconnect() + except Exception as e: + logger.error(f"Error disconnecting transport for session {session_id}: {e}") + + # Remove from shared backend + if self._backend == "redis": + try: + await self._redis.delete(f"mcp:session:{session_id}") + # Notify other workers + await self._redis.publish("mcp_session_events", json.dumps({"type": "remove", "session_id": session_id, "timestamp": time.time()})) + except Exception as e: + logger.error(f"Redis error removing session {session_id}: {e}") + + elif self._backend == "database": + try: + + def _db_remove(): + db_session = next(get_db()) + try: + db_session.query(SessionRecord).filter(SessionRecord.session_id == session_id).delete() + db_session.commit() + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + await asyncio.to_thread(_db_remove) + except Exception as e: + logger.error(f"Database error removing session {session_id}: {e}") + + logger.info(f"Removed session: {session_id}") + + async def broadcast(self, session_id: str, message: dict) -> None: + """Broadcast a session_id and message to a channel. + + Args: + session_id: Session ID + message: Message to broadcast + """ + # Skip for none and memory backend + if self._backend == "none": + return + + if self._backend == "memory": + if isinstance(message, (dict, list)): + msg_json = json.dumps(message) + else: + msg_json = json.dumps(str(message)) + + self._session_message = {"session_id": session_id, "message": msg_json} + + elif self._backend == "redis": + try: + if isinstance(message, (dict, list)): + msg_json = json.dumps(message) + else: + msg_json = json.dumps(str(message)) + + await self._redis.publish(session_id, json.dumps({"type": "message", "message": msg_json, "timestamp": time.time()})) + except Exception as e: + logger.error(f"Redis error during broadcast: {e}") + elif self._backend == "database": + try: + if isinstance(message, (dict, list)): + msg_json = json.dumps(message) + else: + msg_json = json.dumps(str(message)) + + def _db_add(): + db_session = next(get_db()) + try: + message_record = SessionMessageRecord(session_id=session_id, message=msg_json) + db_session.add(message_record) + db_session.commit() + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + await asyncio.to_thread(_db_add) + except Exception as e: + logger.error(f"Database error during broadcast: {e}") + + def get_session_sync(self, session_id: str) -> Any: + """Get session synchronously (not checking shared backend). + + This is a non-blocking method for handlers that need quick access. + It only checks the local cache, not the shared backend. + + Args: + session_id: Session identifier + + Returns: + Transport object or None if not found + """ + # Skip for none backend + if self._backend == "none": + return None + + return self._sessions.get(session_id) + + async def respond( + self, + server_id: Optional[str], + user: json, + session_id: str, + ) -> None: + """Respond to broadcast message is transport relevant to session_id is found locally + + Args: + server_id: Server ID + session_id: Session ID + user: User information + + """ + + if self._backend == "none": + pass + + elif self._backend == "memory": + # if self._session_message: + transport = self.get_session_sync(session_id) + if transport: + message = json.loads(self._session_message.get("message")) + await self.generate_response(message=message, transport=transport, server_id=server_id, user=user) + + elif self._backend == "redis": + await self._pubsub.subscribe(session_id) + + try: + async for msg in self._pubsub.listen(): + if msg["type"] != "message": + continue + data = json.loads(msg["data"]) + message = data.get("message", {}) + if isinstance(message, str): + message = json.loads(message) + transport = self.get_session_sync(session_id) + if transport: + await self.generate_response(message=message, transport=transport, server_id=server_id, user=user) + except asyncio.CancelledError: + logger.info(f"PubSub listener for session {session_id} cancelled") + finally: + await self._pubsub.unsubscribe(session_id) + logger.info(f"Cleaned up pubsub for session {session_id}") + + elif self._backend == "database": + + def _db_read_session(session_id): + db_session = next(get_db()) + try: + # Delete sessions that haven't been accessed for TTL seconds + result = db_session.query(SessionRecord).filter_by(session_id=session_id).first() + return result + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + def _db_read(session_id): + db_session = next(get_db()) + try: + # Delete sessions that haven't been accessed for TTL seconds + result = db_session.query(SessionMessageRecord).filter_by(session_id=session_id).first() + return result + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + def _db_remove(session_id, message): + db_session = next(get_db()) + try: + db_session.query(SessionMessageRecord).filter(SessionMessageRecord.session_id == session_id).filter(SessionMessageRecord.message == message).delete() + db_session.commit() + logger.info("Removed message from mcp_messages table") + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + async def message_check_loop(session_id): + while True: + record = await asyncio.to_thread(_db_read, session_id) + + if record: + message = json.loads(record.message) + transport = self.get_session_sync(session_id) + if transport: + logger.info("Ready to respond") + await self.generate_response(message=message, transport=transport, server_id=server_id, user=user) + + await asyncio.to_thread(_db_remove, session_id, record.message) + + session_exists = await asyncio.to_thread(_db_read_session, session_id) + if not session_exists: + break + + await asyncio.sleep(0.1) + + asyncio.create_task(message_check_loop(session_id)) + + async def _refresh_redis_sessions(self) -> None: + """Refresh TTLs for Redis sessions and clean up disconnected sessions.""" + try: + # Check all local sessions + local_transports = {} + async with self._lock: + local_transports = self._sessions.copy() + + for session_id, transport in local_transports.items(): + try: + if await transport.is_connected(): + # Refresh TTL in Redis + await self._redis.expire(f"mcp:session:{session_id}", self._session_ttl) + else: + # Remove disconnected session + await self.remove_session(session_id) + except Exception as e: + logger.error(f"Error refreshing session {session_id}: {e}") + + except Exception as e: + logger.error(f"Error in Redis session refresh: {e}") + + async def _db_cleanup_task(self) -> None: + """Periodically clean up expired database sessions.""" + logger.info("Starting database cleanup task") + while True: + try: + # Clean up expired sessions every 5 minutes + def _db_cleanup(): + db_session = next(get_db()) + try: + # Delete sessions that haven't been accessed for TTL seconds + expiry_time = func.now() - func.make_interval(seconds=self._session_ttl) # pylint: disable=not-callable + result = db_session.query(SessionRecord).filter(SessionRecord.last_accessed < expiry_time).delete() + db_session.commit() + return result + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + deleted = await asyncio.to_thread(_db_cleanup) + if deleted > 0: + logger.info(f"Cleaned up {deleted} expired database sessions") + + # Check local sessions against database + local_transports = {} + async with self._lock: + local_transports = self._sessions.copy() + + for session_id, transport in local_transports.items(): + try: + if not await transport.is_connected(): + await self.remove_session(session_id) + continue + + # Refresh session in database + def _refresh_session(): + db_session = next(get_db()) + try: + session = db_session.query(SessionRecord).filter(SessionRecord.session_id == session_id).first() + + if session: + # Update last_accessed + session.last_accessed = func.now() # pylint: disable=not-callable + db_session.commit() + return True + return False + except Exception as ex: + db_session.rollback() + raise ex + finally: + db_session.close() + + session_exists = await asyncio.to_thread(_refresh_session) + if not session_exists: + # Session no longer in database, remove locally + await self.remove_session(session_id) + + except Exception as e: + logger.error(f"Error checking session {session_id}: {e}") + + await asyncio.sleep(300) # Run every 5 minutes + + except asyncio.CancelledError: + logger.info("Database cleanup task cancelled") + break + except Exception as e: + logger.error(f"Error in database cleanup task: {e}") + await asyncio.sleep(600) # Sleep longer on error + + async def _memory_cleanup_task(self) -> None: + """Periodically clean up disconnected sessions.""" + logger.info("Starting memory cleanup task") + while True: + try: + # Check all local sessions + local_transports = {} + async with self._lock: + local_transports = self._sessions.copy() + + for session_id, transport in local_transports.items(): + try: + if not await transport.is_connected(): + await self.remove_session(session_id) + except Exception as e: + logger.error(f"Error checking session {session_id}: {e}") + await self.remove_session(session_id) + + await asyncio.sleep(60) # Run every minute + + except asyncio.CancelledError: + logger.info("Memory cleanup task cancelled") + break + except Exception as e: + logger.error(f"Error in memory cleanup task: {e}") + await asyncio.sleep(300) # Sleep longer on error + + # Handle initialize logic + async def handle_initialize_logic(self, body: dict) -> InitializeResult: + """ + Validates the protocol version from the request body and returns an InitializeResult with server capabilities and info. + + Args: + body (dict): The incoming request body. + + Raises: + HTTPException: If the protocol version is missing or unsupported. + + Returns: + InitializeResult: Initialization result with protocol version, capabilities, and server info. + """ + protocol_version = body.get("protocol_version") or body.get("protocolVersion") + # body.get("capabilities", {}) + # body.get("client_info") or body.get("clientInfo", {}) + + if not protocol_version: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Missing protocol version", + headers={"MCP-Error-Code": "-32002"}, + ) + + if protocol_version != settings.protocol_version: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Unsupported protocol version: {protocol_version}", + headers={"MCP-Error-Code": "-32003"}, + ) + + return InitializeResult( + protocolVersion=settings.protocol_version, + capabilities=ServerCapabilities( + prompts={"listChanged": True}, + resources={"subscribe": True, "listChanged": True}, + tools={"listChanged": True}, + logging={}, + roots={"listChanged": True}, + sampling={}, + ), + serverInfo=Implementation(name=settings.app_name, version="1.0.0"), + instructions=("MCP Gateway providing federated tools, resources and prompts. Use /admin interface for configuration."), + ) + + async def generate_response(self, message: json, transport: SSETransport, server_id: Optional[str], user: dict): + """ + Generates response according to SSE specifications + + Args: + message: Message JSON + transport: Transport where message should be responded in + server_id: Server ID + user: User information + + """ + result = {} + + if "method" in message and "id" in message: + method = message["method"] + params = message.get("params", {}) + req_id = message["id"] + db = next(get_db()) + if method == "initialize": + init_result = await self.handle_initialize_logic(params) + response = { + "jsonrpc": "2.0", + "result": init_result.model_dump(by_alias=True, exclude_none=True), + "id": req_id, + } + await transport.send_message(response) + await transport.send_message( + { + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {}, + } + ) + notifications = [ + "tools/list_changed", + "resources/list_changed", + "prompts/list_changed", + ] + for notification in notifications: + await transport.send_message( + { + "jsonrpc": "2.0", + "method": f"notifications/{notification}", + "params": {}, + } + ) + elif method == "tools/list": + if server_id: + tools = await tool_service.list_server_tools(db, server_id=server_id) + else: + tools = await tool_service.list_tools(db) + result = {"tools": [t.model_dump(by_alias=True, exclude_none=True) for t in tools]} + elif method == "resources/list": + if server_id: + resources = await resource_service.list_server_resources(db, server_id=server_id) + else: + resources = await resource_service.list_resources(db) + result = {"resources": [r.model_dump(by_alias=True, exclude_none=True) for r in resources]} + elif method == "prompts/list": + if server_id: + prompts = await prompt_service.list_server_prompts(db, server_id=server_id) + else: + prompts = await prompt_service.list_prompts(db) + result = {"prompts": [p.model_dump(by_alias=True, exclude_none=True) for p in prompts]} + elif method == "ping": + result = {} + elif method == "tools/call": + rpc_input = { + "jsonrpc": "2.0", + "method": message["params"]["name"], + "params": message["params"]["arguments"], + "id": 1, + } + headers = {"Authorization": f"Bearer {user['token']}", "Content-Type": "application/json"} + async with httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) as client: + rpc_response = await client.post( + f"http://localhost:{settings.port}/rpc", + json=rpc_input, + headers=headers, + ) + result = rpc_response.json() + else: + result = {} + + response = {"jsonrpc": "2.0", "result": result, "id": req_id} + logging.info(f"Sending sse message:{response}") + await transport.send_message(response) diff --git a/mcpgateway/config.py b/mcpgateway/config.py new file mode 100644 index 0000000000..b9b47e6f5b --- /dev/null +++ b/mcpgateway/config.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +"""MCP Gateway Configuration. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module defines configuration settings for the MCP Gateway using Pydantic. +It loads configuration from environment variables with sensible defaults. + +Environment variables: +- APP_NAME: Gateway name (default: "MCP Gateway") +- HOST: Host to bind to (default: "0.0.0.0") +- PORT: Port to listen on (default: 4444) +- DATABASE_URL: SQLite database URL (default: "sqlite:///./mcp.db") +- BASIC_AUTH_USER: Admin username (default: "admin") +- BASIC_AUTH_PASSWORD: Admin password (default: "changeme") +- LOG_LEVEL: Logging level (default: "INFO") +- SKIP_SSL_VERIFY: Disable SSL verification (default: False) +- AUTH_REQUIRED: Require authentication (default: True) +- TRANSPORT_TYPE: Transport mechanisms (default: "all") +- FEDERATION_ENABLED: Enable gateway federation (default: True) +- FEDERATION_DISCOVERY: Enable auto-discovery (default: False) +- FEDERATION_PEERS: List of peer gateway URLs (default: []) +- RESOURCE_CACHE_SIZE: Max cached resources (default: 1000) +- RESOURCE_CACHE_TTL: Cache TTL in seconds (default: 3600) +- TOOL_TIMEOUT: Tool invocation timeout (default: 60) +- PROMPT_CACHE_SIZE: Max cached prompts (default: 100) +- HEALTH_CHECK_INTERVAL: Gateway health check interval (default: 60) +""" + +import json +from functools import lru_cache +from pathlib import Path +from typing import Annotated, Any, Dict, List, Optional, Set, Union + +import jq +from fastapi import HTTPException +from jsonpath_ng.ext import parse +from jsonpath_ng.jsonpath import JSONPath +from pydantic import Field, field_validator +from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict + + +class Settings(BaseSettings): + """MCP Gateway configuration settings.""" + + # Basic Settings + app_name: str = Field("MCP Gateway", env="APP_NAME") + host: str = Field("127.0.0.1", env="HOST") + port: int = Field(4444, env="PORT") + database_url: str = "sqlite:///./mcp.db" + templates_dir: Path = Path("mcpgateway/templates") + static_dir: Path = Path("mcpgateway/static") + app_root_path: str = "" + + # Protocol + protocol_version: str = "2025-03-26" + + # Authentication + basic_auth_user: str = "admin" + basic_auth_password: str = "changeme" + jwt_secret_key: str = "my-test-key" + jwt_algorithm: str = "HS256" + auth_required: bool = True + token_expiry: int = 10080 # minutes + + # Encryption key phrase for auth storage + auth_encryption_secret: str = "my-test-key" + + # UI/Admin Feature Flags + mcpgateway_ui_enabled: bool = True + mcpgateway_admin_api_enabled: bool = True + + # Security + skip_ssl_verify: bool = False + + # For allowed_origins, strip '' to ensure we're passing on valid JSON via env + # Tell pydantic *not* to touch this env var – our validator will. + allowed_origins: Annotated[Set[str], NoDecode] = { + "http://localhost", + "http://localhost:4444", + } + + @field_validator("allowed_origins", mode="before") + @classmethod + def _parse_allowed_origins(cls, v): + if isinstance(v, str): + v = v.strip() + if v[:1] in "\"'" and v[-1:] == v[:1]: # strip 1 outer quote pair + v = v[1:-1] + try: + parsed = set(json.loads(v)) + except json.JSONDecodeError: + parsed = {s.strip() for s in v.split(",") if s.strip()} + return parsed + return set(v) + + # Logging + log_level: str = "INFO" + log_format: str = "json" # json or text + log_file: Optional[Path] = None + + # Transport + transport_type: str = "all" # http, ws, sse, all + websocket_ping_interval: int = 30 # seconds + sse_retry_timeout: int = 5000 # milliseconds + + # Federation + federation_enabled: bool = True + federation_discovery: bool = False + + # For federation_peers strip out quotes to ensure we're passing valid JSON via env + federation_peers: Annotated[List[str], NoDecode] = [] + + @field_validator("federation_peers", mode="before") + @classmethod + def _parse_federation_peers(cls, v): + if isinstance(v, str): + v = v.strip() + if v[:1] in "\"'" and v[-1:] == v[:1]: + v = v[1:-1] + try: + peers = json.loads(v) + except json.JSONDecodeError: + peers = [s.strip() for s in v.split(",") if s.strip()] + return peers + return list(v) + + federation_timeout: int = 30 # seconds + federation_sync_interval: int = 300 # seconds + + # Resources + resource_cache_size: int = 1000 + resource_cache_ttl: int = 3600 # seconds + max_resource_size: int = 10 * 1024 * 1024 # 10MB + allowed_mime_types: Set[str] = { + "text/plain", + "text/markdown", + "text/html", + "application/json", + "application/xml", + "image/png", + "image/jpeg", + "image/gif", + } + + # Tools + tool_timeout: int = 60 # seconds + max_tool_retries: int = 3 + tool_rate_limit: int = 100 # requests per minute + tool_concurrent_limit: int = 10 + + # Prompts + prompt_cache_size: int = 100 + max_prompt_size: int = 100 * 1024 # 100KB + prompt_render_timeout: int = 10 # seconds + + # Health Checks + health_check_interval: int = 60 # seconds + health_check_timeout: int = 10 # seconds + unhealthy_threshold: int = 3 + + # Default Roots + default_roots: List[str] = [] + + # Database + db_pool_size: int = 200 + db_max_overflow: int = 10 + db_pool_timeout: int = 30 + db_pool_recycle: int = 3600 + + # Cache + cache_type: str = "database" # memory or redis or database + redis_url: Optional[str] = "redis://localhost:6379/0" + cache_prefix: str = "mcpgw:" + session_ttl: int = 3600 + message_ttl: int = 600 + + # Development + dev_mode: bool = False + reload: bool = False + debug: bool = False + + model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore") + + @property + def api_key(self) -> str: + """Generate API key from auth credentials. + + Returns: + str: API key string in the format "username:password". + """ + return f"{self.basic_auth_user}:{self.basic_auth_password}" + + @property + def supports_http(self) -> bool: + """Check if HTTP transport is enabled. + + Returns: + bool: True if HTTP transport is enabled, False otherwise. + """ + return self.transport_type in ["http", "all"] + + @property + def supports_websocket(self) -> bool: + """Check if WebSocket transport is enabled. + + Returns: + bool: True if WebSocket transport is enabled, False otherwise. + """ + return self.transport_type in ["ws", "all"] + + @property + def supports_sse(self) -> bool: + """Check if SSE transport is enabled. + + Returns: + bool: True if SSE transport is enabled, False otherwise. + """ + return self.transport_type in ["sse", "all"] + + @property + def database_settings(self) -> dict: + """Get SQLAlchemy database settings. + + Returns: + dict: Dictionary containing SQLAlchemy database configuration options. + """ + return { + "pool_size": self.db_pool_size, + "max_overflow": self.db_max_overflow, + "pool_timeout": self.db_pool_timeout, + "pool_recycle": self.db_pool_recycle, + "connect_args": {"check_same_thread": False} if self.database_url.startswith("sqlite") else {}, + } + + @property + def cors_settings(self) -> dict: + """Get CORS settings. + + Returns: + dict: Dictionary containing CORS configuration options. + """ + return ( + { + "allow_origins": list(self.allowed_origins), + "allow_credentials": True, + "allow_methods": ["*"], + "allow_headers": ["*"], + } + if self.cors_enabled + else {} + ) + + def validate_transport(self) -> None: + """Validate transport configuration. + + Raises: + ValueError: If the transport type is not one of the valid options. + """ + valid_types = {"http", "ws", "sse", "all"} + if self.transport_type not in valid_types: + raise ValueError(f"Invalid transport type. Must be one of: {valid_types}") + + def validate_database(self) -> None: + """Validate database configuration.""" + if self.database_url.startswith("sqlite"): + db_path = Path(self.database_url.replace("sqlite:///", "")) + db_dir = db_path.parent + if not db_dir.exists(): + db_dir.mkdir(parents=True) + + +def extract_using_jq(data, jq_filter=""): + """ + Extracts data from a given input (string, dict, or list) using a jq filter string. + + Args: + data (str, dict, list): The input JSON data. Can be a string, dict, or list. + jq_filter (str): The jq filter string to extract the desired data. + + Returns: + The result of applying the jq filter to the input data. + """ + if jq_filter == "": + return data + if isinstance(data, str): + # If the input is a string, parse it as JSON + try: + data = json.loads(data) + except json.JSONDecodeError: + return ["Invalid JSON string provided."] + + elif not isinstance(data, (dict, list)): + # If the input is not a string, dict, or list, raise an error + return ["Input data must be a JSON string, dictionary, or list."] + + # Apply the jq filter to the data + try: + # Pylint can't introspect C-extension modules, so it doesn't know that jq really does export an all() function. + # pylint: disable=c-extension-no-member + result = jq.all(jq_filter, data) # Use `jq.all` to get all matches (returns a list) + if result == [None]: + result = "Error applying jsonpath filter" + except Exception as e: + message = "Error applying jsonpath filter: " + str(e) + return message + + return result + + +def jsonpath_modifier(data: Any, jsonpath: str = "$[*]", mappings: Optional[Dict[str, str]] = None) -> Union[List, Dict]: + """ + Applies the given JSONPath expression and mappings to the data. + Only return data that is required by the user dynamically. + + Args: + data: The JSON data to query. + jsonpath: The JSONPath expression to apply. + mappings: Optional dictionary of mappings where keys are new field names + and values are JSONPath expressions. + + Returns: + Union[List, Dict]: A list (or mapped list) or a Dict of extracted data. + + Raises: + HTTPException: If there's an error parsing or executing the JSONPath expressions. + """ + if not jsonpath: + jsonpath = "$[*]" + + try: + main_expr: JSONPath = parse(jsonpath) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Invalid main JSONPath expression: {e}") + + try: + main_matches = main_expr.find(data) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Error executing main JSONPath: {e}") + + results = [match.value for match in main_matches] + + if mappings: + mapped_results = [] + for item in results: + mapped_item = {} + for new_key, mapping_expr_str in mappings.items(): + try: + mapping_expr = parse(mapping_expr_str) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Invalid mapping JSONPath for key '{new_key}': {e}") + try: + mapping_matches = mapping_expr.find(item) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Error executing mapping JSONPath for key '{new_key}': {e}") + if not mapping_matches: + mapped_item[new_key] = None + elif len(mapping_matches) == 1: + mapped_item[new_key] = mapping_matches[0].value + else: + mapped_item[new_key] = [m.value for m in mapping_matches] + mapped_results.append(mapped_item) + results = mapped_results + + if len(results) == 1 and isinstance(results[0], dict): + return results[0] + return results + + +@lru_cache() +def get_settings() -> Settings: + """Get cached settings instance. + + Returns: + Settings: A cached instance of the Settings class. + """ + # Instantiate a fresh Pydantic Settings object, + # loading from env vars or .env exactly once. + cfg = Settings() + # Validate that transport_type is correct; will + # raise if mis-configured. + cfg.validate_transport() + # Ensure sqlite DB directories exist if needed. + cfg.validate_database() + # Return the one-and-only Settings instance (cached). + return cfg + + +# Create settings instance +settings = get_settings() diff --git a/mcpgateway/db.py b/mcpgateway/db.py new file mode 100644 index 0000000000..a10ae2c652 --- /dev/null +++ b/mcpgateway/db.py @@ -0,0 +1,1050 @@ +# -*- coding: utf-8 -*- +"""MCP Gateway Database Models. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module defines SQLAlchemy models for storing MCP entities including: +- Tools with input schema validation +- Resources with subscription tracking +- Prompts with argument templates +- Federated gateways with capability tracking + +Updated to record server associations independently using many-to-many relationships, +and to record tool execution metrics. +""" + +import re +from datetime import datetime +from typing import Any, Dict, List, Optional + +import jsonschema +from sqlalchemy import ( + JSON, + Boolean, + Column, + DateTime, + Float, + ForeignKey, + Integer, + String, + Table, + Text, + create_engine, + func, + select, +) +from sqlalchemy.event import listen +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import ( + DeclarativeBase, + Mapped, + mapped_column, + relationship, + sessionmaker, +) + +from mcpgateway.config import settings +from mcpgateway.types import ResourceContent + +# Create SQLAlchemy engine with connection pooling +engine = create_engine( + settings.database_url, + pool_size=settings.db_pool_size, + max_overflow=settings.db_max_overflow, + pool_timeout=settings.db_pool_timeout, + pool_recycle=settings.db_pool_recycle, + connect_args={"check_same_thread": False} if settings.database_url.startswith("sqlite") else {}, +) + +# Session factory +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + +class Base(DeclarativeBase): + """Base class for all models.""" + + +# Association table for tools and gateways (federation) +tool_gateway_table = Table( + "tool_gateway_association", + Base.metadata, + Column("tool_id", Integer, ForeignKey("tools.id"), primary_key=True), + Column("gateway_id", Integer, ForeignKey("gateways.id"), primary_key=True), +) + +# Association table for resources and gateways (federation) +resource_gateway_table = Table( + "resource_gateway_association", + Base.metadata, + Column("resource_id", Integer, ForeignKey("resources.id"), primary_key=True), + Column("gateway_id", Integer, ForeignKey("gateways.id"), primary_key=True), +) + +# Association table for prompts and gateways (federation) +prompt_gateway_table = Table( + "prompt_gateway_association", + Base.metadata, + Column("prompt_id", Integer, ForeignKey("prompts.id"), primary_key=True), + Column("gateway_id", Integer, ForeignKey("gateways.id"), primary_key=True), +) + +# Association table for servers and tools +server_tool_association = Table( + "server_tool_association", + Base.metadata, + Column("server_id", Integer, ForeignKey("servers.id"), primary_key=True), + Column("tool_id", Integer, ForeignKey("tools.id"), primary_key=True), +) + +# Association table for servers and resources +server_resource_association = Table( + "server_resource_association", + Base.metadata, + Column("server_id", Integer, ForeignKey("servers.id"), primary_key=True), + Column("resource_id", Integer, ForeignKey("resources.id"), primary_key=True), +) + +# Association table for servers and prompts +server_prompt_association = Table( + "server_prompt_association", + Base.metadata, + Column("server_id", Integer, ForeignKey("servers.id"), primary_key=True), + Column("prompt_id", Integer, ForeignKey("prompts.id"), primary_key=True), +) + + +class ToolMetric(Base): + """ + ORM model for recording individual metrics for tool executions. + + Each record in this table corresponds to a single tool invocation and records: + - timestamp (datetime): When the invocation occurred. + - response_time (float): The execution time in seconds. + - is_success (bool): True if the execution succeeded, False otherwise. + - error_message (Optional[str]): Error message if the execution failed. + + Aggregated metrics (such as total executions, successful/failed counts, failure rate, + minimum, maximum, and average response times, and last execution time) should be computed + on the fly using SQL aggregate functions over the rows in this table. + """ + + __tablename__ = "tool_metrics" + + id: Mapped[int] = mapped_column(primary_key=True) + tool_id: Mapped[int] = mapped_column(Integer, ForeignKey("tools.id"), nullable=False) + timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + response_time: Mapped[float] = mapped_column(Float, nullable=False) + is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) + error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + + # Relationship back to the Tool model. + tool: Mapped["Tool"] = relationship("Tool", back_populates="metrics") + + +class ResourceMetric(Base): + """ + ORM model for recording metrics for resource invocations. + + Attributes: + id (int): Primary key. + resource_id (int): Foreign key linking to the resource. + timestamp (datetime): The time when the invocation occurred. + response_time (float): The response time in seconds. + is_success (bool): True if the invocation succeeded, False otherwise. + error_message (Optional[str]): Error message if the invocation failed. + """ + + __tablename__ = "resource_metrics" + + id: Mapped[int] = mapped_column(primary_key=True) + resource_id: Mapped[int] = mapped_column(Integer, ForeignKey("resources.id"), nullable=False) + timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + response_time: Mapped[float] = mapped_column(Float, nullable=False) + is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) + error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + + # Relationship back to the Resource model. + resource: Mapped["Resource"] = relationship("Resource", back_populates="metrics") + + +class ServerMetric(Base): + """ + ORM model for recording metrics for server invocations. + + Attributes: + id (int): Primary key. + server_id (int): Foreign key linking to the server. + timestamp (datetime): The time when the invocation occurred. + response_time (float): The response time in seconds. + is_success (bool): True if the invocation succeeded, False otherwise. + error_message (Optional[str]): Error message if the invocation failed. + """ + + __tablename__ = "server_metrics" + + id: Mapped[int] = mapped_column(primary_key=True) + server_id: Mapped[int] = mapped_column(Integer, ForeignKey("servers.id"), nullable=False) + timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + response_time: Mapped[float] = mapped_column(Float, nullable=False) + is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) + error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + + # Relationship back to the Server model. + server: Mapped["Server"] = relationship("Server", back_populates="metrics") + + +class PromptMetric(Base): + """ + ORM model for recording metrics for prompt invocations. + + Attributes: + id (int): Primary key. + prompt_id (int): Foreign key linking to the prompt. + timestamp (datetime): The time when the invocation occurred. + response_time (float): The response time in seconds. + is_success (bool): True if the invocation succeeded, False otherwise. + error_message (Optional[str]): Error message if the invocation failed. + """ + + __tablename__ = "prompt_metrics" + + id: Mapped[int] = mapped_column(primary_key=True) + prompt_id: Mapped[int] = mapped_column(Integer, ForeignKey("prompts.id"), nullable=False) + timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + response_time: Mapped[float] = mapped_column(Float, nullable=False) + is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) + error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + + # Relationship back to the Prompt model. + prompt: Mapped["Prompt"] = relationship("Prompt", back_populates="metrics") + + +class Tool(Base): + """ + ORM model for a registered Tool. + + Supports both local tools and federated tools from other gateways. + The integration_type field indicates the tool format: + - "MCP" for MCP-compliant tools (default) + - "ICA" for non-MCP ICA Integrations + + Additionally, this model provides computed properties for aggregated metrics based + on the associated ToolMetric records. These include: + - execution_count: Total number of invocations. + - successful_executions: Count of successful invocations. + - failed_executions: Count of failed invocations. + - failure_rate: Ratio of failed invocations to total invocations. + - min_response_time: Fastest recorded response time. + - max_response_time: Slowest recorded response time. + - avg_response_time: Mean response time. + - last_execution_time: Timestamp of the most recent invocation. + + The property `metrics_summary` returns a dictionary with these aggregated values. + + The following fields have been added to support tool invocation configuration: + - request_type: HTTP method to use when invoking the tool. + - auth_type: Type of authentication ("basic", "bearer", or None). + - auth_username: Username for basic authentication. + - auth_password: Password for basic authentication. + - auth_token: Token for bearer token authentication. + - auth_header_key: header key for authentication. + - auth_header_value: header value for authentication. + """ + + __tablename__ = "tools" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + url: Mapped[str] + description: Mapped[Optional[str]] + integration_type: Mapped[str] = mapped_column(default="MCP") + headers: Mapped[Optional[Dict[str, str]]] = mapped_column(JSON) + input_schema: Mapped[Dict[str, Any]] = mapped_column(JSON) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + is_active: Mapped[bool] = mapped_column(default=True) + jsonpath_filter: Mapped[str] = mapped_column(default="") + + # Request type and authentication fields + request_type: Mapped[str] = mapped_column(default="SSE") + auth_type: Mapped[Optional[str]] = mapped_column(default=None) # "basic", "bearer", or None + auth_value: Mapped[Optional[str]] = mapped_column(default=None) + + # Federation relationship with a local gateway + gateway_id: Mapped[Optional[int]] = mapped_column(ForeignKey("gateways.id")) + gateway = relationship("Gateway", back_populates="tools") + federated_with = relationship("Gateway", secondary=tool_gateway_table, back_populates="federated_tools") + + # Many-to-many relationship with Servers + servers = relationship("Server", secondary=server_tool_association, back_populates="tools") + + # Relationship with ToolMetric records + metrics: Mapped[List["ToolMetric"]] = relationship("ToolMetric", back_populates="tool", cascade="all, delete-orphan") + + @hybrid_property + def execution_count(self) -> int: + """ + Returns the number of times the tool has been executed, + calculated from the associated ToolMetric records. + + Returns: + int: The total count of tool executions. + """ + return len(self.metrics) + + @execution_count.expression + # method is intentionally a class-level expression, so no `self` + # pylint: disable=no-self-argument + def execution_count(cls): + """ + SQL expression to compute the execution count for the tool. + + Returns: + int: Returns execution count of a given tool + """ + return select(func.count(ToolMetric.id)).where(ToolMetric.tool_id == cls.id).label("execution_count") # pylint: disable=not-callable + + @property + def successful_executions(self) -> int: + """ + Returns the count of successful tool executions, + computed from the associated ToolMetric records. + + Returns: + int: The count of successful tool executions. + """ + return sum(1 for m in self.metrics if m.is_success) + + @property + def failed_executions(self) -> int: + """ + Returns the count of failed tool executions, + computed from the associated ToolMetric records. + + Returns: + int: The count of failed tool executions. + """ + return sum(1 for m in self.metrics if not m.is_success) + + @property + def failure_rate(self) -> float: + """ + Returns the failure rate (as a float between 0 and 1) computed as: + (failed executions) / (total executions). + Returns 0.0 if there are no executions. + + Returns: + float: The failure rate as a value between 0 and 1. + """ + total: int = self.execution_count + # execution_count is a @hybrid_property, not a callable here + if total == 0: # pylint: disable=comparison-with-callable + return 0.0 + return self.failed_executions / total + + @property + def min_response_time(self) -> Optional[float]: + """ + Returns the minimum response time among all tool executions. + Returns None if no executions exist. + + Returns: + Optional[float]: The minimum response time, or None if no executions exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return min(times) if times else None + + @property + def max_response_time(self) -> Optional[float]: + """ + Returns the maximum response time among all tool executions. + Returns None if no executions exist. + + Returns: + Optional[float]: The maximum response time, or None if no executions exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return max(times) if times else None + + @property + def avg_response_time(self) -> Optional[float]: + """ + Returns the average response time among all tool executions. + Returns None if no executions exist. + + Returns: + Optional[float]: The average response time, or None if no executions exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return sum(times) / len(times) if times else None + + @property + def last_execution_time(self) -> Optional[datetime]: + """ + Returns the timestamp of the most recent tool execution. + Returns None if no executions exist. + + Returns: + Optional[datetime]: The timestamp of the most recent execution, or None if no executions exist. + """ + if not self.metrics: + return None + return max(m.timestamp for m in self.metrics) + + @property + def metrics_summary(self) -> Dict[str, Any]: + """ + Returns aggregated metrics for the tool as a dictionary with the following keys: + - total_executions: Total number of invocations. + - successful_executions: Number of successful invocations. + - failed_executions: Number of failed invocations. + - failure_rate: Failure rate (failed/total) or 0.0 if no invocations. + - min_response_time: Minimum response time (or None if no invocations). + - max_response_time: Maximum response time (or None if no invocations). + - avg_response_time: Average response time (or None if no invocations). + - last_execution_time: Timestamp of the most recent invocation (or None). + + Returns: + Dict[str, Any]: Dictionary containing the aggregated metrics. + """ + return { + "total_executions": self.execution_count, + "successful_executions": self.successful_executions, + "failed_executions": self.failed_executions, + "failure_rate": self.failure_rate, + "min_response_time": self.min_response_time, + "max_response_time": self.max_response_time, + "avg_response_time": self.avg_response_time, + "last_execution_time": self.last_execution_time, + } + + +class Resource(Base): + """ + ORM model for a registered Resource. + + Resources represent content that can be read by clients. + Supports subscriptions for real-time updates. + Additionally, this model provides a relationship with ResourceMetric records + to capture invocation metrics (such as execution counts, response times, and failures). + """ + + __tablename__ = "resources" + + id: Mapped[int] = mapped_column(primary_key=True) + uri: Mapped[str] = mapped_column(unique=True) + name: Mapped[str] + description: Mapped[Optional[str]] + mime_type: Mapped[Optional[str]] + size: Mapped[Optional[int]] + template: Mapped[Optional[str]] # URI template for parameterized resources + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + is_active: Mapped[bool] = mapped_column(default=True) + metrics: Mapped[List["ResourceMetric"]] = relationship("ResourceMetric", back_populates="resource", cascade="all, delete-orphan") + + # Content storage - can be text or binary + text_content: Mapped[Optional[str]] = mapped_column(Text) + binary_content: Mapped[Optional[bytes]] + + # Subscription tracking + subscriptions: Mapped[List["ResourceSubscription"]] = relationship("ResourceSubscription", back_populates="resource", cascade="all, delete-orphan") + + gateway_id: Mapped[Optional[int]] = mapped_column(ForeignKey("gateways.id")) + gateway = relationship("Gateway", back_populates="resources") + federated_with = relationship("Gateway", secondary=resource_gateway_table, back_populates="federated_resources") + + # Many-to-many relationship with Servers + servers = relationship("Server", secondary=server_resource_association, back_populates="resources") + + @property + def content(self) -> ResourceContent: + """ + Returns the resource content in the appropriate format. + + If text content exists, returns a ResourceContent with text. + Otherwise, if binary content exists, returns a ResourceContent with blob data. + Raises a ValueError if no content is available. + + Returns: + ResourceContent: The resource content with appropriate format (text or blob). + + Raises: + ValueError: If the resource has no content available. + """ + + if self.text_content is not None: + return ResourceContent( + type="resource", + uri=self.uri, + mime_type=self.mime_type, + text=self.text_content, + ) + if self.binary_content is not None: + return ResourceContent( + type="resource", + uri=self.uri, + mime_type=self.mime_type or "application/octet-stream", + blob=self.binary_content, + ) + raise ValueError("Resource has no content") + + @property + def execution_count(self) -> int: + """ + Returns the number of times the resource has been invoked, + calculated from the associated ResourceMetric records. + + Returns: + int: The total count of resource invocations. + """ + return len(self.metrics) + + @property + def successful_executions(self) -> int: + """ + Returns the count of successful resource invocations, + computed from the associated ResourceMetric records. + + Returns: + int: The count of successful resource invocations. + """ + return sum(1 for m in self.metrics if m.is_success) + + @property + def failed_executions(self) -> int: + """ + Returns the count of failed resource invocations, + computed from the associated ResourceMetric records. + + Returns: + int: The count of failed resource invocations. + """ + return sum(1 for m in self.metrics if not m.is_success) + + @property + def failure_rate(self) -> float: + """ + Returns the failure rate (as a float between 0 and 1) computed as: + (failed invocations) / (total invocations). + Returns 0.0 if there are no invocations. + + Returns: + float: The failure rate as a value between 0 and 1. + """ + total: int = self.execution_count + if total == 0: + return 0.0 + return self.failed_executions / total + + @property + def min_response_time(self) -> Optional[float]: + """ + Returns the minimum response time among all resource invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The minimum response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return min(times) if times else None + + @property + def max_response_time(self) -> Optional[float]: + """ + Returns the maximum response time among all resource invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The maximum response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return max(times) if times else None + + @property + def avg_response_time(self) -> Optional[float]: + """ + Returns the average response time among all resource invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The average response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return sum(times) / len(times) if times else None + + @property + def last_execution_time(self) -> Optional[datetime]: + """ + Returns the timestamp of the most recent resource invocation. + Returns None if no invocations exist. + + Returns: + Optional[datetime]: The timestamp of the most recent invocation, or None if no invocations exist. + """ + if not self.metrics: + return None + return max(m.timestamp for m in self.metrics) + + +class ResourceSubscription(Base): + """Tracks subscriptions to resource updates.""" + + __tablename__ = "resource_subscriptions" + + id: Mapped[int] = mapped_column(primary_key=True) + resource_id: Mapped[int] = mapped_column(ForeignKey("resources.id")) + subscriber_id: Mapped[str] # Client identifier + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + last_notification: Mapped[Optional[datetime]] = mapped_column(DateTime) + + resource: Mapped["Resource"] = relationship(back_populates="subscriptions") + + +class Prompt(Base): + """ + ORM model for a registered Prompt template. + + Represents a prompt template along with its argument schema. + Supports rendering and invocation of prompts. + Additionally, this model provides computed properties for aggregated metrics based + on the associated PromptMetric records. These include: + - execution_count: Total number of prompt invocations. + - successful_executions: Count of successful invocations. + - failed_executions: Count of failed invocations. + - failure_rate: Ratio of failed invocations to total invocations. + - min_response_time: Fastest recorded response time. + - max_response_time: Slowest recorded response time. + - avg_response_time: Mean response time. + - last_execution_time: Timestamp of the most recent invocation. + """ + + __tablename__ = "prompts" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + description: Mapped[Optional[str]] + template: Mapped[str] = mapped_column(Text) + argument_schema: Mapped[Dict[str, Any]] = mapped_column(JSON) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + is_active: Mapped[bool] = mapped_column(default=True) + metrics: Mapped[List["PromptMetric"]] = relationship("PromptMetric", back_populates="prompt", cascade="all, delete-orphan") + + gateway_id: Mapped[Optional[int]] = mapped_column(ForeignKey("gateways.id")) + gateway = relationship("Gateway", back_populates="prompts") + federated_with = relationship("Gateway", secondary=prompt_gateway_table, back_populates="federated_prompts") + + # Many-to-many relationship with Servers + servers = relationship("Server", secondary=server_prompt_association, back_populates="prompts") + + def validate_arguments(self, args: Dict[str, str]) -> None: + """ + Validate prompt arguments against the argument schema. + + Args: + args (Dict[str, str]): Dictionary of arguments to validate. + + Raises: + ValueError: If the arguments do not conform to the schema. + + """ + try: + jsonschema.validate(args, self.argument_schema) + except jsonschema.exceptions.ValidationError as e: + raise ValueError(f"Invalid prompt arguments: {str(e)}") + + @property + def execution_count(self) -> int: + """ + Returns the number of times the prompt has been invoked, + calculated from the associated PromptMetric records. + + Returns: + int: The total count of prompt invocations. + """ + return len(self.metrics) + + @property + def successful_executions(self) -> int: + """ + Returns the count of successful prompt invocations, + computed from the associated PromptMetric records. + + Returns: + int: The count of successful prompt invocations. + """ + return sum(1 for m in self.metrics if m.is_success) + + @property + def failed_executions(self) -> int: + """ + Returns the count of failed prompt invocations, + computed from the associated PromptMetric records. + + Returns: + int: The count of failed prompt invocations. + """ + return sum(1 for m in self.metrics if not m.is_success) + + @property + def failure_rate(self) -> float: + """ + Returns the failure rate (as a float between 0 and 1) computed as: + (failed invocations) / (total invocations). + Returns 0.0 if there are no invocations. + + Returns: + float: The failure rate as a value between 0 and 1. + """ + total: int = self.execution_count + if total == 0: + return 0.0 + return self.failed_executions / total + + @property + def min_response_time(self) -> Optional[float]: + """ + Returns the minimum response time among all prompt invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The minimum response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return min(times) if times else None + + @property + def max_response_time(self) -> Optional[float]: + """ + Returns the maximum response time among all prompt invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The maximum response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return max(times) if times else None + + @property + def avg_response_time(self) -> Optional[float]: + """ + Returns the average response time among all prompt invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The average response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return sum(times) / len(times) if times else None + + @property + def last_execution_time(self) -> Optional[datetime]: + """ + Returns the timestamp of the most recent prompt invocation. + Returns None if no invocations exist. + + Returns: + Optional[datetime]: The timestamp of the most recent invocation, or None if no invocations exist. + """ + if not self.metrics: + return None + return max(m.timestamp for m in self.metrics) + + +class Server(Base): + """ + ORM model for MCP Servers Catalog. + + Represents a server that composes catalog items (tools, resources, prompts). + Additionally, this model provides computed properties for aggregated metrics based + on the associated ServerMetric records. These include: + - execution_count: Total number of invocations. + - successful_executions: Count of successful invocations. + - failed_executions: Count of failed invocations. + - failure_rate: Ratio of failed invocations to total invocations. + - min_response_time: Fastest recorded response time. + - max_response_time: Slowest recorded response time. + - avg_response_time: Mean response time. + - last_execution_time: Timestamp of the most recent invocation. + """ + + __tablename__ = "servers" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + description: Mapped[Optional[str]] + icon: Mapped[Optional[str]] + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + is_active: Mapped[bool] = mapped_column(default=True) + metrics: Mapped[List["ServerMetric"]] = relationship("ServerMetric", back_populates="server", cascade="all, delete-orphan") + + # Many-to-many relationships for associated items + tools = relationship("Tool", secondary=server_tool_association, back_populates="servers") + resources = relationship("Resource", secondary=server_resource_association, back_populates="servers") + prompts = relationship("Prompt", secondary=server_prompt_association, back_populates="servers") + + @property + def execution_count(self) -> int: + """ + Returns the number of times the server has been invoked, + calculated from the associated ServerMetric records. + + Returns: + int: The total count of server invocations. + """ + return len(self.metrics) + + @property + def successful_executions(self) -> int: + """ + Returns the count of successful server invocations, + computed from the associated ServerMetric records. + + Returns: + int: The count of successful server invocations. + """ + return sum(1 for m in self.metrics if m.is_success) + + @property + def failed_executions(self) -> int: + """ + Returns the count of failed server invocations, + computed from the associated ServerMetric records. + + Returns: + int: The count of failed server invocations. + """ + return sum(1 for m in self.metrics if not m.is_success) + + @property + def failure_rate(self) -> float: + """ + Returns the failure rate (as a float between 0 and 1) computed as: + (failed invocations) / (total invocations). + Returns 0.0 if there are no invocations. + + Returns: + float: The failure rate as a value between 0 and 1. + """ + total: int = self.execution_count + if total == 0: + return 0.0 + return self.failed_executions / total + + @property + def min_response_time(self) -> Optional[float]: + """ + Returns the minimum response time among all server invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The minimum response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return min(times) if times else None + + @property + def max_response_time(self) -> Optional[float]: + """ + Returns the maximum response time among all server invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The maximum response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return max(times) if times else None + + @property + def avg_response_time(self) -> Optional[float]: + """ + Returns the average response time among all server invocations. + Returns None if no invocations exist. + + Returns: + Optional[float]: The average response time, or None if no invocations exist. + """ + times: List[float] = [m.response_time for m in self.metrics] + return sum(times) / len(times) if times else None + + @property + def last_execution_time(self) -> Optional[datetime]: + """ + Returns the timestamp of the most recent server invocation. + Returns None if no invocations exist. + + Returns: + Optional[datetime]: The timestamp of the most recent invocation, or None if no invocations exist. + """ + if not self.metrics: + return None + return max(m.timestamp for m in self.metrics) + + +class Gateway(Base): + """ORM model for a federated peer Gateway.""" + + __tablename__ = "gateways" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + url: Mapped[str] + description: Mapped[Optional[str]] + capabilities: Mapped[Dict[str, Any]] = mapped_column(JSON) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + is_active: Mapped[bool] = mapped_column(default=True) + last_seen: Mapped[Optional[datetime]] + + # Relationship with local tools this gateway provides + tools: Mapped[List["Tool"]] = relationship(back_populates="gateway", cascade="all, delete-orphan") + + # Relationship with local prompts this gateway provides + prompts: Mapped[List["Prompt"]] = relationship(back_populates="gateway", cascade="all, delete-orphan") + + # Relationship with local resources this gateway provides + resources: Mapped[List["Resource"]] = relationship(back_populates="gateway", cascade="all, delete-orphan") + + # Tools federated from this gateway + federated_tools: Mapped[List["Tool"]] = relationship(secondary=tool_gateway_table, back_populates="federated_with") + + # Prompts federated from this resource + federated_resources: Mapped[List["Resource"]] = relationship(secondary=resource_gateway_table, back_populates="federated_with") + + # Prompts federated from this gateway + federated_prompts: Mapped[List["Prompt"]] = relationship(secondary=prompt_gateway_table, back_populates="federated_with") + + # Authorizations + auth_type: Mapped[Optional[str]] = mapped_column(default=None) # "basic", "bearer", "headers" or None + auth_value: Mapped[Optional[Dict[str, str]]] = mapped_column(JSON) + + +class SessionRecord(Base): + """ORM model for sessions from SSE client.""" + + __tablename__ = "mcp_sessions" + + session_id: Mapped[str] = mapped_column(primary_key=True) + created_at: Mapped[datetime] = mapped_column(default=func.now()) # pylint: disable=not-callable + last_accessed: Mapped[datetime] = mapped_column(default=func.now(), onupdate=func.now()) # pylint: disable=not-callable + data: Mapped[str] = mapped_column(String, nullable=True) + + +class SessionMessageRecord(Base): + """ORM model for messages from SSE client.""" + + __tablename__ = "mcp_messages" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + session_id: Mapped[str] = mapped_column(String) + message: Mapped[str] = mapped_column(String, nullable=True) + created_at: Mapped[datetime] = mapped_column(default=func.now()) # pylint: disable=not-callable + last_accessed: Mapped[datetime] = mapped_column(default=func.now(), onupdate=func.now()) # pylint: disable=not-callable + + +# Event listeners for validation +def validate_tool_schema(mapper, connection, target): + """ + Validate tool schema before insert/update. + + Args: + mapper: The mapper being used for the operation. + connection: The database connection. + target: The target object being validated. + + Raises: + ValueError: If the tool input schema is invalid. + """ + # You can use mapper and connection later, if required. + _ = mapper + _ = connection + if hasattr(target, "input_schema"): + try: + jsonschema.Draft7Validator.check_schema(target.input_schema) + except jsonschema.exceptions.SchemaError as e: + raise ValueError(f"Invalid tool input schema: {str(e)}") + + +def validate_tool_name(mapper, connection, target): + """ + Validate tool name before insert/update. Check if the name matches the required pattern. + + Args: + mapper: The mapper being used for the operation. + connection: The database connection. + target: The target object being validated. + + Raises: + ValueError: If the tool name contains invalid characters. + """ + # You can use mapper and connection later, if required. + _ = mapper + _ = connection + if hasattr(target, "name"): + if not re.match(r"^[a-zA-Z0-9_-]+$", target.name): + raise ValueError(f"Invalid tool name '{target.name}'. Only alphanumeric characters, hyphens, and underscores are allowed.") + + +def validate_prompt_schema(mapper, connection, target): + """ + Validate prompt argument schema before insert/update. + + Args: + mapper: The mapper being used for the operation. + connection: The database connection. + target: The target object being validated. + + Raises: + ValueError: If the prompt argument schema is invalid. + """ + # You can use mapper and connection later, if required. + _ = mapper + _ = connection + if hasattr(target, "argument_schema"): + try: + jsonschema.Draft7Validator.check_schema(target.argument_schema) + except jsonschema.exceptions.SchemaError as e: + raise ValueError(f"Invalid prompt argument schema: {str(e)}") + + +# Register validation listeners + +listen(Tool, "before_insert", validate_tool_schema) +listen(Tool, "before_update", validate_tool_schema) +listen(Tool, "before_insert", validate_tool_name) +listen(Tool, "before_update", validate_tool_name) +listen(Prompt, "before_insert", validate_prompt_schema) +listen(Prompt, "before_update", validate_prompt_schema) + + +def get_db(): + """ + Dependency to get database session. + + Yields: + SessionLocal: A SQLAlchemy database session. + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + + +# Create all tables +def init_db(): + """ + Initialize database tables. + + Raises: + Exception: If database initialization fails. + """ + try: + Base.metadata.create_all(bind=engine) + except SQLAlchemyError as e: + raise Exception(f"Failed to initialize database: {str(e)}") diff --git a/mcpgateway/federation/__init__.py b/mcpgateway/federation/__init__.py new file mode 100644 index 0000000000..dc4f5f6da5 --- /dev/null +++ b/mcpgateway/federation/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +"""Federation Package. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Exposes components for MCP Gateway federation including: +- Gateway discovery +- Request forwarding +- Federation management +""" + +from mcpgateway.federation.discovery import DiscoveryService +from mcpgateway.federation.forward import ForwardingService +from mcpgateway.federation.manager import ( + FederationError, + FederationManager, +) + +__all__ = [ + "DiscoveryService", + "ForwardingService", + "FederationManager", + "FederationError", +] diff --git a/mcpgateway/federation/discovery.py b/mcpgateway/federation/discovery.py new file mode 100644 index 0000000000..6c86cb4719 --- /dev/null +++ b/mcpgateway/federation/discovery.py @@ -0,0 +1,390 @@ +# -*- coding: utf-8 -*- +"""Federation Discovery Service. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements automatic peer discovery for MCP Gateways. +It supports multiple discovery mechanisms: +- DNS-SD service discovery +- Static peer lists +- Peer exchange protocol +- Manual registration +""" + +import asyncio +import logging +import os +import socket +from dataclasses import dataclass +from datetime import datetime, timedelta +from typing import Dict, List, Optional +from urllib.parse import urlparse + +import httpx +from zeroconf import ServiceInfo, ServiceStateChange +from zeroconf.asyncio import AsyncServiceBrowser, AsyncZeroconf + +from mcpgateway.config import settings +from mcpgateway.types import ServerCapabilities + +logger = logging.getLogger(__name__) + +PROTOCOL_VERSION = os.getenv("PROTOCOL_VERSION", "2025-03-26") + + +@dataclass +class DiscoveredPeer: + """Information about a discovered peer gateway.""" + + url: str + name: Optional[str] + protocol_version: Optional[str] + capabilities: Optional[ServerCapabilities] + discovered_at: datetime + last_seen: datetime + source: str + + +class LocalDiscoveryService: + """Super class for DiscoveryService""" + + def __init__(self): + """Initialize local discovery service""" + # Service info for local discovery + self._service_type = "_mcp._tcp.local." + self._service_info = ServiceInfo( + self._service_type, + f"{settings.app_name}.{self._service_type}", + addresses=[socket.inet_aton(addr) for addr in self._get_local_addresses()], + port=settings.port, + properties={ + "name": settings.app_name, + "version": "1.0.0", + "protocol": PROTOCOL_VERSION, + }, + ) + + def _get_local_addresses(self) -> List[str]: + """Get list of local network addresses. + + Returns: + List of IP addresses + """ + addresses = [] + try: + # Get all network interfaces + for iface in socket.getaddrinfo(socket.gethostname(), None): + addr = iface[4][0] + # Skip localhost + if not addr.startswith("127."): + addresses.append(addr) + except Exception as e: + logger.warning(f"Failed to get local addresses: {e}") + # Fall back to localhost + addresses.append("127.0.0.1") + + return addresses or ["127.0.0.1"] + + +class DiscoveryService(LocalDiscoveryService): + """Service for automatic gateway discovery. + + Supports multiple discovery mechanisms: + - DNS-SD for local network discovery + - Static peer lists from configuration + - Peer exchange with known gateways + - Manual registration via API + """ + + def __init__(self): + """Initialize discovery service.""" + super().__init__() + + self._zeroconf: Optional[AsyncZeroconf] = None + self._browser: Optional[AsyncServiceBrowser] = None + self._http_client = httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) + + # Track discovered peers + self._discovered_peers: Dict[str, DiscoveredPeer] = {} + + # Start background tasks + self._cleanup_task: Optional[asyncio.Task] = None + self._refresh_task: Optional[asyncio.Task] = None + + async def start(self) -> None: + """ + Start discovery service. + + Raises: + Exception: If unable to start discovery service + """ + try: + # Initialize DNS-SD + if settings.federation_discovery: + self._zeroconf = AsyncZeroconf() + await self._zeroconf.async_register_service(self._service_info) + self._browser = AsyncServiceBrowser( + self._zeroconf.zeroconf, + self._service_type, + handlers=[self._on_service_state_change], + ) + + # Start background tasks + self._cleanup_task = asyncio.create_task(self._cleanup_loop()) + self._refresh_task = asyncio.create_task(self._refresh_loop()) + + # Load static peers + for peer_url in settings.federation_peers: + await self.add_peer(peer_url, source="static") + + logger.info("Discovery service started") + + except Exception as e: + logger.error(f"Failed to start discovery service: {e}") + await self.stop() + raise + + async def stop(self) -> None: + """Stop discovery service.""" + # Cancel background tasks + if self._cleanup_task: + self._cleanup_task.cancel() + try: + await self._cleanup_task + except asyncio.CancelledError: + pass + + if self._refresh_task: + self._refresh_task.cancel() + try: + await self._refresh_task + except asyncio.CancelledError: + pass + + # Stop DNS-SD + if self._browser: + await self._browser.async_cancel() + self._browser = None + + if self._zeroconf: + await self._zeroconf.async_unregister_service(self._service_info) + await self._zeroconf.async_close() + self._zeroconf = None + + # Close HTTP client + await self._http_client.aclose() + + logger.info("Discovery service stopped") + + async def add_peer(self, url: str, source: str, name: Optional[str] = None) -> bool: + """Add a new peer gateway. + + Args: + url: Gateway URL + source: Discovery source + name: Optional gateway name + + Returns: + True if peer was added + """ + # Validate URL + try: + parsed = urlparse(url) + if not parsed.scheme or not parsed.netloc: + logger.warning(f"Invalid peer URL: {url}") + return False + except Exception: + logger.warning(f"Failed to parse peer URL: {url}") + return False + + # Skip if already known + if url in self._discovered_peers: + peer = self._discovered_peers[url] + peer.last_seen = datetime.utcnow() + return False + + try: + # Try to get gateway info + capabilities = await self._get_gateway_info(url) + + # Add to discovered peers + self._discovered_peers[url] = DiscoveredPeer( + url=url, + name=name, + protocol_version=PROTOCOL_VERSION, + capabilities=capabilities, + discovered_at=datetime.utcnow(), + last_seen=datetime.utcnow(), + source=source, + ) + + logger.info(f"Added peer gateway: {url} (via {source})") + return True + + except Exception as e: + logger.warning(f"Failed to add peer {url}: {e}") + return False + + def get_discovered_peers(self) -> List[DiscoveredPeer]: + """Get list of discovered peers. + + Returns: + List of discovered peer information + """ + return list(self._discovered_peers.values()) + + async def refresh_peer(self, url: str) -> bool: + """Refresh peer gateway information. + + Args: + url: Gateway URL to refresh + + Returns: + True if refresh succeeded + """ + if url not in self._discovered_peers: + return False + + try: + capabilities = await self._get_gateway_info(url) + self._discovered_peers[url].capabilities = capabilities + self._discovered_peers[url].last_seen = datetime.utcnow() + return True + except Exception as e: + logger.warning(f"Failed to refresh peer {url}: {e}") + return False + + async def remove_peer(self, url: str) -> None: + """Remove a peer gateway. + + Args: + url: Gateway URL to remove + """ + self._discovered_peers.pop(url, None) + + async def _on_service_state_change( + self, + zeroconf: AsyncZeroconf, + service_type: str, + name: str, + state_change: ServiceStateChange, + ) -> None: + """Handle DNS-SD service changes. + + Args: + zeroconf: Zeroconf instance + service_type: Service type + name: Service name + state_change: Type of state change + """ + if state_change is ServiceStateChange.Added: + info = await zeroconf.async_get_service_info(service_type, name) + if info: + try: + # Extract gateway info + addresses = [socket.inet_ntoa(addr) for addr in info.addresses] + if addresses: + port = info.port + url = f"http://{addresses[0]}:{port}" + name = info.properties.get(b"name", b"").decode() + + # Add peer + await self.add_peer(url, source="dns-sd", name=name) + + except Exception as e: + logger.warning(f"Failed to process discovered service {name}: {e}") + + async def _cleanup_loop(self) -> None: + """Periodically clean up stale peers.""" + while True: + try: + now = datetime.utcnow() + stale_urls = [url for url, peer in self._discovered_peers.items() if now - peer.last_seen > timedelta(minutes=10)] + for url in stale_urls: + await self.remove_peer(url) + logger.info(f"Removed stale peer: {url}") + + except Exception as e: + logger.error(f"Peer cleanup error: {e}") + + await asyncio.sleep(60) + + async def _refresh_loop(self) -> None: + """Periodically refresh peer information.""" + while True: + try: + # Refresh all peers + for url in list(self._discovered_peers.keys()): + await self.refresh_peer(url) + + # Exchange peers + await self._exchange_peers() + + except Exception as e: + logger.error(f"Peer refresh error: {e}") + + await asyncio.sleep(300) # 5 minutes + + async def _get_gateway_info(self, url: str) -> ServerCapabilities: + """Get gateway capabilities. + + Args: + url: Gateway URL + + Returns: + Gateway capabilities + + Raises: + ValueError: If protocol version is unsupported + """ + # Build initialize request + request = { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocol_version": PROTOCOL_VERSION, + "capabilities": {"roots": {"listChanged": True}, "sampling": {}}, + "client_info": {"name": settings.app_name, "version": "1.0.0"}, + }, + } + + # Send request using the persistent HTTP client directly + response = await self._http_client.post(f"{url}/initialize", json=request, headers=self._get_auth_headers()) + response.raise_for_status() + result = response.json() + + # Validate response + if result.get("protocol_version") != PROTOCOL_VERSION: + raise ValueError(f"Unsupported protocol version: {result.get('protocol_version')}") + + return ServerCapabilities.parse_obj(result["capabilities"]) + + async def _exchange_peers(self) -> None: + """Exchange peer lists with known gateways.""" + for url in list(self._discovered_peers.keys()): + try: + # Get peer's peer list using the persistent HTTP client directly + response = await self._http_client.get(f"{url}/peers", headers=self._get_auth_headers()) + response.raise_for_status() + peers = response.json() + + # Add new peers from the response + for peer in peers: + if isinstance(peer, dict) and "url" in peer: + await self.add_peer(peer["url"], source="exchange", name=peer.get("name")) + + except Exception as e: + logger.warning(f"Failed to exchange peers with {url}: {e}") + + def _get_auth_headers(self) -> Dict[str, str]: + """ + Get headers for gateway authentication. + + Returns: + dict: Authorization header dict + """ + api_key = f"{settings.basic_auth_user}:{settings.basic_auth_password}" + return {"Authorization": f"Basic {api_key}", "X-API-Key": api_key} diff --git a/mcpgateway/federation/forward.py b/mcpgateway/federation/forward.py new file mode 100644 index 0000000000..214853b64b --- /dev/null +++ b/mcpgateway/federation/forward.py @@ -0,0 +1,337 @@ +# -*- coding: utf-8 -*- +"""Federation Request Forwarding. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements request forwarding for federated MCP Gateways. +It handles: +- Request routing to appropriate gateways +- Response aggregation +- Error handling and retry logic +- Request/response transformation +""" + +import asyncio +import logging +from datetime import datetime +from typing import Any, Dict, List, Optional, Set, Tuple, Union + +import httpx +from sqlalchemy import select +from sqlalchemy.orm import Session + +from mcpgateway.config import settings +from mcpgateway.db import Gateway as DbGateway +from mcpgateway.db import Tool as DbTool +from mcpgateway.types import ToolResult + +logger = logging.getLogger(__name__) + + +class ForwardingError(Exception): + """Base class for forwarding-related errors.""" + + +class ForwardingService: + """Service for handling request forwarding across gateways. + + Handles: + - Request routing + - Response aggregation + - Error handling + - Request transformation + """ + + def __init__(self): + """Initialize forwarding service.""" + self._http_client = httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) + + # Track active requests + self._active_requests: Dict[str, asyncio.Task] = {} + + # Request history for rate limiting + self._request_history: Dict[str, List[datetime]] = {} + + # Cache gateway information + self._gateway_tools: Dict[int, Set[str]] = {} + + async def start(self) -> None: + """Start forwarding service.""" + logger.info("Request forwarding service started") + + async def stop(self) -> None: + """Stop forwarding service.""" + # Cancel active requests + for request_id, task in self._active_requests.items(): + logger.info(f"Cancelling request {request_id}") + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + await self._http_client.aclose() + logger.info("Request forwarding service stopped") + + async def forward_request( + self, + db: Session, + method: str, + params: Optional[Dict[str, Any]] = None, + target_gateway_id: Optional[int] = None, + ) -> Any: + """Forward a request to gateway(s). + + Args: + db: Database session + method: RPC method name + params: Optional method parameters + target_gateway_id: Optional specific gateway ID + + Returns: + Forwarded response(s) + + Raises: + ForwardingError: If forwarding fails + """ + try: + if target_gateway_id: + # Forward to specific gateway + return await self._forward_to_gateway(db, target_gateway_id, method, params) + + # Forward to all relevant gateways + return await self._forward_to_all(db, method, params) + + except Exception as e: + raise ForwardingError(f"Forward request failed: {str(e)}") + + async def forward_tool_request(self, db: Session, tool_name: str, arguments: Dict[str, Any]) -> ToolResult: + """Forward a tool invocation request. + + Args: + db: Database session + tool_name: Tool to invoke + arguments: Tool arguments + + Returns: + Tool result + + Raises: + ForwardingError: If forwarding fails + """ + try: + # Find tool + tool = db.execute(select(DbTool).where(DbTool.name == tool_name).where(DbTool.is_active)).scalar_one_or_none() + + if not tool: + raise ForwardingError(f"Tool not found: {tool_name}") + + if not tool.gateway_id: + raise ForwardingError(f"Tool {tool_name} is not federated") + + # Forward to gateway + result = await self._forward_to_gateway( + db, + tool.gateway_id, + "tools/invoke", + {"name": tool_name, "arguments": arguments}, + ) + + # Parse result + return ToolResult( + content=result.get("content", []), + is_error=result.get("is_error", False), + ) + + except Exception as e: + raise ForwardingError(f"Failed to forward tool request: {str(e)}") + + async def forward_resource_request(self, db: Session, uri: str) -> Tuple[Union[str, bytes], str]: + """Forward a resource read request. + + Args: + db: Database session + uri: Resource URI + + Returns: + Tuple of (content, mime_type) + + Raises: + ForwardingError: If forwarding fails + """ + try: + # Find gateway for resource + gateway = await self._find_resource_gateway(db, uri) + if not gateway: + raise ForwardingError(f"No gateway found for resource: {uri}") + + # Forward request + result = await self._forward_to_gateway(db, gateway.id, "resources/read", {"uri": uri}) + + # Parse result + if "text" in result: + return result["text"], result.get("mime_type", "text/plain") + if "blob" in result: + return result["blob"], result.get("mime_type", "application/octet-stream") + + raise ForwardingError("Invalid resource response format") + + except Exception as e: + raise ForwardingError(f"Failed to forward resource request: {str(e)}") + + async def _forward_to_gateway( + self, + db: Session, + gateway_id: int, + method: str, + params: Optional[Dict[str, Any]] = None, + ) -> Any: + """Forward request to a specific gateway. + + Args: + db: Database session + gateway_id: Gateway to forward to + method: RPC method name + params: Optional method parameters + + Returns: + Gateway response + + Raises: + ForwardingError: If forwarding fails + httpx.TimeoutException: If unable to connect after retries + """ + # Get gateway + gateway = db.get(DbGateway, gateway_id) + if not gateway or not gateway.is_active: + raise ForwardingError(f"Gateway not found: {gateway_id}") + + # Check rate limits + if not self._check_rate_limit(gateway.url): + raise ForwardingError("Rate limit exceeded") + + try: + # Build request + request = {"jsonrpc": "2.0", "id": 1, "method": method} + if params: + request["params"] = params + + # Send request with retries using the persistent client directly + for attempt in range(settings.max_tool_retries): + try: + response = await self._http_client.post( + f"{gateway.url}/rpc", + json=request, + headers=self._get_auth_headers(), + ) + response.raise_for_status() + result = response.json() + + # Update last seen + gateway.last_seen = datetime.utcnow() + + # Handle response + if "error" in result: + raise ForwardingError(f"Gateway error: {result['error'].get('message')}") + return result.get("result") + + except httpx.TimeoutException: + if attempt == settings.max_tool_retries - 1: + raise + await asyncio.sleep(1 * (attempt + 1)) + + except Exception as e: + raise ForwardingError(f"Failed to forward to {gateway.name}: {str(e)}") + + async def _forward_to_all(self, db: Session, method: str, params: Optional[Dict[str, Any]] = None) -> List[Any]: + """Forward request to all active gateways. + + Args: + db: Database session + method: RPC method name + params: Optional method parameters + + Returns: + List of responses + + Raises: + ForwardingError: If all forwards fail + """ + # Get active gateways + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + # Forward to each gateway + results = [] + errors = [] + + for gateway in gateways: + try: + result = await self._forward_to_gateway(db, gateway.id, method, params) + results.append(result) + except Exception as e: + errors.append(str(e)) + + if not results and errors: + raise ForwardingError(f"All forwards failed: {'; '.join(errors)}") + + return results + + async def _find_resource_gateway(self, db: Session, uri: str) -> Optional[DbGateway]: + """Find gateway hosting a resource. + + Args: + db: Database session + uri: Resource URI + + Returns: + Gateway record or None + """ + # Get active gateways + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + # Check each gateway + for gateway in gateways: + try: + resources = await self._forward_to_gateway(db, gateway.id, "resources/list") + for resource in resources: + if resource.get("uri") == uri: + return gateway + except Exception as e: + logger.error(f"Failed to check gateway {gateway.name} for resource {uri}: {str(e)}") + continue + + return None + + def _check_rate_limit(self, gateway_url: str) -> bool: + """Check if gateway request is within rate limits. + + Args: + gateway_url: Gateway URL + + Returns: + True if request allowed + """ + now = datetime.utcnow() + + # Clean old history + self._request_history[gateway_url] = [t for t in self._request_history.get(gateway_url, []) if (now - t).total_seconds() < 60] + + # Check limit + if len(self._request_history[gateway_url]) >= settings.tool_rate_limit: + return False + + # Record request + self._request_history[gateway_url].append(now) + return True + + def _get_auth_headers(self) -> Dict[str, str]: + """ + Get headers for gateway authentication. + + Returns: + dict: Authorization header dict + """ + api_key = f"{settings.basic_auth_user}:{settings.basic_auth_password}" + return {"Authorization": f"Basic {api_key}", "X-API-Key": api_key} diff --git a/mcpgateway/federation/manager.py b/mcpgateway/federation/manager.py new file mode 100644 index 0000000000..1d8224b63a --- /dev/null +++ b/mcpgateway/federation/manager.py @@ -0,0 +1,459 @@ +# -*- coding: utf-8 -*- +"""Federation Manager. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module provides the core federation management system for the MCP Gateway. +It coordinates: +- Gateway discovery and registration +- Capability synchronization +- Request forwarding +- Health monitoring + +The federation manager serves as the central point for all federation-related +operations, coordinating with discovery, sync and forwarding components. +""" + +import asyncio +import logging +import os +from datetime import datetime, timedelta +from typing import Any, Dict, List, Optional, Set + +import httpx +from sqlalchemy import select +from sqlalchemy.orm import Session + +from mcpgateway.config import settings +from mcpgateway.db import Gateway as DbGateway +from mcpgateway.db import Tool as DbTool +from mcpgateway.federation.discovery import DiscoveryService +from mcpgateway.types import ( + ClientCapabilities, + Implementation, + InitializeRequest, + InitializeResult, + Prompt, + Resource, + ServerCapabilities, + Tool, +) + +logger = logging.getLogger(__name__) + +PROTOCOL_VERSION = os.getenv("PROTOCOL_VERSION", "2025-03-26") + + +class FederationError(Exception): + """Base class for federation-related errors.""" + + +class FederationManager: + """Manages federation across MCP gateways. + + Coordinates: + - Peer discovery and registration + - Capability synchronization + - Request forwarding + - Health monitoring + """ + + def __init__(self): + """Initialize federation manager.""" + self._discovery = DiscoveryService() + self._http_client = httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) + + # Track active gateways + self._active_gateways: Set[str] = set() + + # Background tasks + self._sync_task: Optional[asyncio.Task] = None + self._health_task: Optional[asyncio.Task] = None + + async def start(self, db: Session) -> None: + """Start federation system. + + Args: + db: Database session + + Raises: + Exception: If unable to start federation manager + """ + if not settings.federation_enabled: + logger.info("Federation disabled by configuration") + return + + try: + # Start discovery + await self._discovery.start() + + # Load existing gateways + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + for gateway in gateways: + self._active_gateways.add(gateway.url) + + # Start background tasks + self._sync_task = asyncio.create_task(self._run_sync_loop(db)) + self._health_task = asyncio.create_task(self._run_health_loop(db)) + + logger.info("Federation manager started") + + except Exception as e: + logger.error(f"Failed to start federation manager: {e}") + await self.stop() + raise + + async def stop(self) -> None: + """Stop federation system.""" + # Stop background tasks + if self._sync_task: + self._sync_task.cancel() + try: + await self._sync_task + except asyncio.CancelledError: + pass + + if self._health_task: + self._health_task.cancel() + try: + await self._health_task + except asyncio.CancelledError: + pass + + # Stop discovery + await self._discovery.stop() + + # Close HTTP client + await self._http_client.aclose() + + logger.info("Federation manager stopped") + + async def register_gateway(self, db: Session, url: str, name: Optional[str] = None) -> DbGateway: + """Register a new gateway. + + Args: + db: Database session + url: Gateway URL + name: Optional gateway name + + Returns: + Registered gateway record + + Raises: + FederationError: If registration fails + """ + try: + # Initialize connection + capabilities = await self._initialize_gateway(url) + gateway_name = name or f"Gateway-{len(self._active_gateways) + 1}" + + # Create gateway record + gateway = DbGateway( + name=gateway_name, + url=url, + capabilities=capabilities.dict(), + last_seen=datetime.utcnow(), + ) + db.add(gateway) + db.commit() + db.refresh(gateway) + + # Update tracking + self._active_gateways.add(url) + + # Add to discovery + await self._discovery.add_peer(url, source="manual", name=gateway_name) + + logger.info(f"Registered gateway: {gateway_name} ({url})") + return gateway + + except Exception as e: + db.rollback() + raise FederationError(f"Failed to register gateway: {str(e)}") + + async def unregister_gateway(self, db: Session, gateway_id: int) -> None: + """Unregister a gateway. + + Args: + db: Database session + gateway_id: Gateway ID to unregister + + Raises: + FederationError: If unregistration fails + """ + try: + # Find gateway + gateway = db.get(DbGateway, gateway_id) + if not gateway: + raise FederationError(f"Gateway not found: {gateway_id}") + + # Remove gateway + gateway.is_active = False + gateway.updated_at = datetime.utcnow() + + # Remove associated tools + db.execute(select(DbTool).where(DbTool.gateway_id == gateway_id)).delete() + + db.commit() + + # Update tracking + self._active_gateways.discard(gateway.url) + + # Remove from discovery + await self._discovery.remove_peer(gateway.url) + + logger.info(f"Unregistered gateway: {gateway.name}") + + except Exception as e: + db.rollback() + raise FederationError(f"Failed to unregister gateway: {str(e)}") + + async def get_gateway_tools(self, db: Session, gateway_id: int) -> List[Tool]: + """Get tools provided by a gateway. + + Args: + db: Database session + gateway_id: Gateway ID + + Returns: + List of gateway tools + + Raises: + FederationError: If tool list cannot be retrieved + """ + gateway = db.get(DbGateway, gateway_id) + if not gateway or not gateway.is_active: + raise FederationError(f"Gateway not found: {gateway_id}") + + try: + # Get tool list + tools = await self.forward_request(gateway, "tools/list") + return [Tool.parse_obj(t) for t in tools] + + except Exception as e: + raise FederationError(f"Failed to get tools from {gateway.name}: {str(e)}") + + async def get_gateway_resources(self, db: Session, gateway_id: int) -> List[Resource]: + """Get resources provided by a gateway. + + Args: + db: Database session + gateway_id: Gateway ID + + Returns: + List of gateway resources + + Raises: + FederationError: If resource list cannot be retrieved + """ + gateway = db.get(DbGateway, gateway_id) + if not gateway or not gateway.is_active: + raise FederationError(f"Gateway not found: {gateway_id}") + + try: + # Get resource list + resources = await self.forward_request(gateway, "resources/list") + return [Resource.parse_obj(r) for r in resources] + + except Exception as e: + raise FederationError(f"Failed to get resources from {gateway.name}: {str(e)}") + + async def get_gateway_prompts(self, db: Session, gateway_id: int) -> List[Prompt]: + """Get prompts provided by a gateway. + + Args: + db: Database session + gateway_id: Gateway ID + + Returns: + List of gateway prompts + + Raises: + FederationError: If prompt list cannot be retrieved + """ + gateway = db.get(DbGateway, gateway_id) + if not gateway or not gateway.is_active: + raise FederationError(f"Gateway not found: {gateway_id}") + + try: + # Get prompt list + prompts = await self.forward_request(gateway, "prompts/list") + return [Prompt.parse_obj(p) for p in prompts] + + except Exception as e: + raise FederationError(f"Failed to get prompts from {gateway.name}: {str(e)}") + + async def forward_request(self, gateway: DbGateway, method: str, params: Optional[Dict[str, Any]] = None) -> Any: + """Forward a request to a gateway. + + Args: + gateway: Gateway to forward to + method: RPC method name + params: Optional method parameters + + Returns: + Gateway response + + Raises: + FederationError: If request forwarding fails + """ + try: + # Build request + request = {"jsonrpc": "2.0", "id": 1, "method": method} + if params: + request["params"] = params + + # Send request using the persistent client directly + response = await self._http_client.post(f"{gateway.url}/rpc", json=request, headers=self._get_auth_headers()) + response.raise_for_status() + result = response.json() + + # Update last seen + gateway.last_seen = datetime.utcnow() + + # Handle response + if "error" in result: + raise FederationError(f"Gateway error: {result['error'].get('message')}") + return result.get("result") + + except Exception as e: + raise FederationError(f"Failed to forward request to {gateway.name}: {str(e)}") + + async def _run_sync_loop(self, db: Session) -> None: + """ + Run periodic gateway synchronization. + + Args: + db: Session object + """ + while True: + try: + # Process discovered peers + discovered = self._discovery.get_discovered_peers() + for peer in discovered: + if peer.url not in self._active_gateways: + try: + await self.register_gateway(db, peer.url, peer.name) + except Exception as e: + logger.warning(f"Failed to register discovered peer {peer.url}: {e}") + + # Sync active gateways + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + for gateway in gateways: + try: + # Update capabilities + capabilities = await self._initialize_gateway(gateway.url) + gateway.capabilities = capabilities.dict() + gateway.last_seen = datetime.utcnow() + gateway.is_active = True + + except Exception as e: + logger.warning(f"Failed to sync gateway {gateway.name}: {e}") + + db.commit() + + except Exception as e: + logger.error(f"Sync loop error: {e}") + db.rollback() + + await asyncio.sleep(settings.federation_sync_interval) + + async def _run_health_loop(self, db: Session) -> None: + """ + Run periodic gateway health checks. + + Args: + db: Session object + """ + while True: + try: + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + for gateway in gateways: + try: + # Check gateway health + await self._check_gateway_health(gateway) + except Exception as e: + logger.warning(f"Health check failed for {gateway.name}: {e}") + # Mark inactive if not seen recently + if datetime.utcnow() - gateway.last_seen > timedelta(minutes=5): + gateway.is_active = False + self._active_gateways.discard(gateway.url) + + db.commit() + + except Exception as e: + logger.error(f"Health check error: {e}") + db.rollback() + + await asyncio.sleep(settings.health_check_interval) + + async def _initialize_gateway(self, url: str) -> ServerCapabilities: + """Initialize connection to a gateway. + + Args: + url: Gateway URL + + Returns: + Gateway capabilities + + Raises: + FederationError: If initialization fails + """ + try: + # Build initialize request + request = InitializeRequest( + protocol_version=PROTOCOL_VERSION, + capabilities=ClientCapabilities(roots={"listChanged": True}, sampling={}), + client_info=Implementation(name=settings.app_name, version="1.0.0"), + ) + + # Send request using the persistent client directly + response = await self._http_client.post( + f"{url}/initialize", + json=request.dict(), + headers=self._get_auth_headers(), + ) + response.raise_for_status() + result = InitializeResult.parse_obj(response.json()) + + # Verify protocol version + if result.protocol_version != PROTOCOL_VERSION: + raise FederationError(f"Unsupported protocol version: {result.protocol_version}") + + return result.capabilities + + except Exception as e: + raise FederationError(f"Failed to initialize gateway: {str(e)}") + + async def _check_gateway_health(self, gateway: DbGateway) -> bool: + """Check if a gateway is healthy. + + Args: + gateway: Gateway to check + + Returns: + True if gateway is healthy + + Raises: + FederationError: If health check fails + """ + try: + await self._initialize_gateway(gateway.url) + return True + except Exception as e: + raise FederationError(f"Gateway health check failed: {str(e)}") + + def _get_auth_headers(self) -> Dict[str, str]: + """ + Get headers for gateway authentication. + + Returns: + dict: Headers to be used in request + """ + api_key = f"{settings.basic_auth_user}:{settings.basic_auth_password}" + return {"Authorization": f"Basic {api_key}", "X-API-Key": api_key} diff --git a/mcpgateway/handlers/__init__.py b/mcpgateway/handlers/__init__.py new file mode 100644 index 0000000000..6b31f3e810 --- /dev/null +++ b/mcpgateway/handlers/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +"""Handlers Package. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Provides request handlers for the MCP Gateway including: +- Sampling request handling +""" + +from mcpgateway.handlers.sampling import SamplingHandler + +__all__ = ["SamplingHandler"] diff --git a/mcpgateway/handlers/sampling.py b/mcpgateway/handlers/sampling.py new file mode 100644 index 0000000000..92106adc4f --- /dev/null +++ b/mcpgateway/handlers/sampling.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- +"""MCP Sampling Handler Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements the sampling handler for MCP LLM interactions. +It handles model selection, sampling preferences, and message generation. +""" + +import logging +from typing import Any, Dict, List + +from sqlalchemy.orm import Session + +from mcpgateway.types import CreateMessageResult, ModelPreferences, Role, TextContent + +logger = logging.getLogger(__name__) + + +class SamplingError(Exception): + """Base class for sampling errors.""" + + +class SamplingHandler: + """MCP sampling request handler. + + Handles: + - Model selection based on preferences + - Message sampling requests + - Context management + - Content validation + """ + + def __init__(self): + """Initialize sampling handler.""" + self._supported_models = { + # Maps model names to capabilities scores (cost, speed, intelligence) + "claude-3-haiku": (0.8, 0.9, 0.7), + "claude-3-sonnet": (0.5, 0.7, 0.9), + "claude-3-opus": (0.2, 0.5, 1.0), + "gemini-1.5-pro": (0.6, 0.8, 0.8), + } + + async def initialize(self) -> None: + """Initialize sampling handler.""" + logger.info("Initializing sampling handler") + + async def shutdown(self) -> None: + """Shutdown sampling handler.""" + logger.info("Shutting down sampling handler") + + async def create_message(self, db: Session, request: Dict[str, Any]) -> CreateMessageResult: + """Create message from sampling request. + + Args: + db: Database session + request: Sampling request parameters + + Returns: + Sampled message result + + Raises: + SamplingError: If sampling fails + """ + try: + # Extract request parameters + messages = request.get("messages", []) + max_tokens = request.get("maxTokens") + model_prefs = ModelPreferences.parse_obj(request.get("modelPreferences", {})) + include_context = request.get("includeContext", "none") + request.get("metadata", {}) + + # Validate request + if not messages: + raise SamplingError("No messages provided") + if not max_tokens: + raise SamplingError("Max tokens not specified") + + # Select model + model = self._select_model(model_prefs) + logger.info(f"Selected model: {model}") + + # Include context if requested + if include_context != "none": + messages = await self._add_context(db, messages, include_context) + + # Validate messages + for msg in messages: + if not self._validate_message(msg): + raise SamplingError(f"Invalid message format: {msg}") + + # TODO: Sample from selected model + # For now return mock response + response = self._mock_sample(messages=messages) + + # Convert to result + return CreateMessageResult( + content=TextContent(type="text", text=response), + model=model, + role=Role.ASSISTANT, + stop_reason="maxTokens", + ) + + except Exception as e: + logger.error(f"Sampling error: {e}") + raise SamplingError(str(e)) + + def _select_model(self, preferences: ModelPreferences) -> str: + """Select model based on preferences. + + Args: + preferences: Model selection preferences + + Returns: + Selected model name + + Raises: + SamplingError: If no suitable model found + """ + # Check model hints first + if preferences.hints: + for hint in preferences.hints: + for model in self._supported_models: + if hint.name and hint.name in model: + return model + + # Score models on preferences + best_score = -1 + best_model = None + + for model, caps in self._supported_models.items(): + cost_score = caps[0] * (1 - preferences.cost_priority) + speed_score = caps[1] * preferences.speed_priority + intel_score = caps[2] * preferences.intelligence_priority + + total_score = (cost_score + speed_score + intel_score) / 3 + + if total_score > best_score: + best_score = total_score + best_model = model + + if not best_model: + raise SamplingError("No suitable model found") + + return best_model + + async def _add_context(self, _db: Session, messages: List[Dict[str, Any]], _context_type: str) -> List[Dict[str, Any]]: + """Add context to messages. + + Args: + db: Database session + messages: Message list + context_type: Context inclusion type + + Returns: + Messages with added context + """ + # TODO: Implement context gathering based on type + # For now return original messages + return messages + + def _validate_message(self, message: Dict[str, Any]) -> bool: + """Validate message format. + + Args: + message: Message to validate + + Returns: + True if valid + """ + try: + # Must have role and content + if "role" not in message or "content" not in message or message["role"] not in ("user", "assistant"): + return False + + # Content must be valid + content = message["content"] + if content.get("type") == "text": + if not isinstance(content.get("text"), str): + return False + elif content.get("type") == "image": + if not (content.get("data") and content.get("mime_type")): + return False + else: + return False + + return True + + except Exception: + return False + + def _mock_sample( + self, + messages: List[Dict[str, Any]], + ) -> str: + """Mock sampling response for testing. + + Args: + messages: Input messages + + Returns: + Sampled response text + """ + # Extract last user message + last_msg = None + for msg in reversed(messages): + if msg["role"] == "user": + last_msg = msg + break + + if not last_msg: + return "I'm not sure what to respond to." + + # Get user text + user_text = "" + content = last_msg["content"] + if content["type"] == "text": + user_text = content["text"] + elif content["type"] == "image": + user_text = "I see the image you shared." + + # Generate simple response + return f"You said: {user_text}\nHere is my response..." diff --git a/mcpgateway/main.py b/mcpgateway/main.py new file mode 100644 index 0000000000..b88195aa22 --- /dev/null +++ b/mcpgateway/main.py @@ -0,0 +1,2034 @@ +# -*- coding: utf-8 -*- +""" +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +MCP Gateway - Main FastAPI Application. + +This module defines the core FastAPI application for the Model Context Protocol (MCP) Gateway. +It serves as the entry point for handling all HTTP and WebSocket traffic. + +Features and Responsibilities: +- Initializes and orchestrates services for tools, resources, prompts, servers, gateways, and roots. +- Supports full MCP protocol operations: initialize, ping, notify, complete, and sample. +- Integrates authentication (JWT and basic), CORS, caching, and middleware. +- Serves a rich Admin UI for managing gateway entities via HTMX-based frontend. +- Exposes routes for JSON-RPC, SSE, and WebSocket transports. +- Manages application lifecycle including startup and graceful shutdown of all services. + +Structure: +- Declares routers for MCP protocol operations and administration. +- Registers dependencies (e.g., DB sessions, auth handlers). +- Applies middleware including custom documentation protection. +- Configures resource caching and session registry using pluggable backends. +- Provides OpenAPI metadata and redirect handling depending on UI feature flags. +""" + +import asyncio +import json +import logging +from contextlib import asynccontextmanager +from typing import Any, AsyncIterator, Dict, List, Optional, Union + +import httpx +from fastapi import ( + APIRouter, + Body, + Depends, + FastAPI, + HTTPException, + Request, + WebSocket, + WebSocketDisconnect, + status, +) +from fastapi.background import BackgroundTasks +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from sqlalchemy import text +from sqlalchemy.orm import Session +from starlette.middleware.base import BaseHTTPMiddleware + +# Import the admin routes from the new module +from mcpgateway.admin import admin_router +from mcpgateway.cache import ResourceCache, SessionRegistry +from mcpgateway.config import jsonpath_modifier, settings +from mcpgateway.db import Base, SessionLocal, engine +from mcpgateway.handlers.sampling import SamplingHandler +from mcpgateway.schemas import ( + GatewayCreate, + GatewayRead, + GatewayUpdate, + JsonPathModifier, + PromptCreate, + PromptRead, + PromptUpdate, + ResourceCreate, + ResourceRead, + ResourceUpdate, + ServerCreate, + ServerRead, + ServerUpdate, + ToolCreate, + ToolRead, + ToolUpdate, +) +from mcpgateway.services.completion_service import CompletionService +from mcpgateway.services.gateway_service import GatewayService +from mcpgateway.services.logging_service import LoggingService +from mcpgateway.services.prompt_service import ( + PromptError, + PromptNameConflictError, + PromptNotFoundError, + PromptService, +) +from mcpgateway.services.resource_service import ( + ResourceError, + ResourceNotFoundError, + ResourceService, + ResourceURIConflictError, +) +from mcpgateway.services.root_service import RootService +from mcpgateway.services.server_service import ( + ServerError, + ServerNameConflictError, + ServerNotFoundError, + ServerService, +) +from mcpgateway.services.tool_service import ( + ToolError, + ToolNameConflictError, + ToolService, +) +from mcpgateway.transports.sse_transport import SSETransport +from mcpgateway.types import ( + InitializeRequest, + InitializeResult, + ListResourceTemplatesResult, + LogLevel, + ResourceContent, + Root, +) +from mcpgateway.utils.verify_credentials import require_auth, require_auth_override +from mcpgateway.validation.jsonrpc import ( + JSONRPCError, + validate_request, +) + +# Initialize logging service first +logging_service = LoggingService() +logger = logging_service.get_logger("mcpgateway") + +# Configure root logger level +logging.basicConfig( + level=getattr(logging, settings.log_level.upper()), + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) + +# Create database tables +Base.metadata.create_all(bind=engine) + +# Initialize services +tool_service = ToolService() +resource_service = ResourceService() +prompt_service = PromptService() +gateway_service = GatewayService() +root_service = RootService() +completion_service = CompletionService() +sampling_handler = SamplingHandler() +server_service = ServerService() + + +# Initialize session registry +session_registry = SessionRegistry( + backend=settings.cache_type, + redis_url=settings.redis_url if settings.cache_type == "redis" else None, + database_url=settings.database_url if settings.cache_type == "database" else None, + session_ttl=settings.session_ttl, + message_ttl=settings.message_ttl, +) + +# Initialize cache +resource_cache = ResourceCache(max_size=settings.resource_cache_size, ttl=settings.resource_cache_ttl) + +# Initialize FastAPI app +app = FastAPI( + title=settings.app_name, + version="1.0.0", + description="A FastAPI-based MCP Gateway with federation support", + root_path=settings.app_root_path, +) + + +class DocsAuthMiddleware(BaseHTTPMiddleware): + """ + Middleware to protect FastAPI's auto-generated documentation routes + (/docs, /redoc, and /openapi.json) using Bearer token authentication. + + If a request to one of these paths is made without a valid token, + the request is rejected with a 401 or 403 error. + """ + + async def dispatch(self, request: Request, call_next): + """ + Intercepts incoming requests to check if they are accessing protected documentation routes. + If so, it requires a valid Bearer token; otherwise, it allows the request to proceed. + + Args: + request (Request): The incoming HTTP request. + call_next (Callable): The function to call the next middleware or endpoint. + + Returns: + Response: Either the standard route response or a 401/403 error response. + """ + protected_paths = ["/docs", "/redoc", "/openapi.json"] + + if any(request.url.path.startswith(p) for p in protected_paths): + try: + token = request.headers.get("Authorization") + cookie_token = request.cookies.get("jwt_token") + + # Simulate what Depends(require_auth) would do + await require_auth_override(token, cookie_token) + except HTTPException as e: + return JSONResponse(status_code=e.status_code, content={"detail": e.detail}, headers=e.headers if e.headers else None) + + # Proceed to next middleware or route + return await call_next(request) + + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"] if not settings.allowed_origins else list(settings.allowed_origins), + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + expose_headers=["Content-Type", "Content-Length"], +) + + +# Add custom DocsAuthMiddleware +app.add_middleware(DocsAuthMiddleware) + +# Set up Jinja2 templates and store in app state for later use +templates = Jinja2Templates(directory=str(settings.templates_dir)) +app.state.templates = templates + +# Create API routers +protocol_router = APIRouter(prefix="/protocol", tags=["Protocol"]) +tool_router = APIRouter(prefix="/tools", tags=["Tools"]) +resource_router = APIRouter(prefix="/resources", tags=["Resources"]) +prompt_router = APIRouter(prefix="/prompts", tags=["Prompts"]) +gateway_router = APIRouter(prefix="/gateways", tags=["Gateways"]) +root_router = APIRouter(prefix="/roots", tags=["Roots"]) +utility_router = APIRouter(tags=["Utilities"]) +server_router = APIRouter(prefix="/servers", tags=["Servers"]) +metrics_router = APIRouter(prefix="/metrics", tags=["Metrics"]) + +# Basic Auth setup + + +# Database dependency +def get_db(): + """ + Dependency function to provide a database session. + + Yields: + Session: A SQLAlchemy session object for interacting with the database. + + Ensures: + The database session is closed after the request completes, even in the case of an exception. + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def require_api_key(api_key: str) -> None: + """ + Validates the provided API key. + + This function checks if the provided API key matches the expected one + based on the settings. If the validation fails, it raises an HTTPException + with a 401 Unauthorized status. + + Args: + api_key (str): The API key provided by the user or client. + + Raises: + HTTPException: If the API key is invalid, a 401 Unauthorized error is raised. + """ + if settings.auth_required: + expected = f"{settings.basic_auth_user}:{settings.basic_auth_password}" + if api_key != expected: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key") + + +async def invalidate_resource_cache(uri: Optional[str] = None) -> None: + """ + Invalidates the resource cache. + + If a specific URI is provided, only that resource will be removed from the cache. + If no URI is provided, the entire resource cache will be cleared. + + Args: + uri (Optional[str]): The URI of the resource to invalidate from the cache. If None, the entire cache is cleared. + """ + if uri: + resource_cache.delete(uri) + else: + resource_cache.clear() + + +################# +# Protocol APIs # +################# +@protocol_router.post("/initialize") +async def initialize(request: Request, user: str = Depends(require_auth)) -> InitializeResult: + """ + Initialize a protocol. + + This endpoint handles the initialization process of a protocol by accepting + a JSON request body and processing it. The `require_auth` dependency ensures that + the user is authenticated before proceeding. + + Args: + request (Request): The incoming request object containing the JSON body. + user (str): The authenticated user (from `require_auth` dependency). + + Returns: + InitializeResult: The result of the initialization process. + + Raises: + HTTPException: If the request body contains invalid JSON, a 400 Bad Request error is raised. + """ + try: + body = await request.json() + + logger.debug(f"Authenticated user {user} is initializing the protocol.") + return await session_registry.handle_initialize_logic(body) + + except json.JSONDecodeError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid JSON in request body", + ) + + +@protocol_router.post("/ping") +async def ping(request: Request, user: str = Depends(require_auth)) -> JSONResponse: + """ + Handle a ping request according to the MCP specification. + + This endpoint expects a JSON-RPC request with the method "ping" and responds + with a JSON-RPC response containing an empty result, as required by the protocol. + + Args: + request (Request): The incoming FastAPI request. + user (str): The authenticated user (dependency injection). + + Returns: + JSONResponse: A JSON-RPC response with an empty result or an error response. + + Raises: + HTTPException: If the request method is not "ping". + """ + try: + body: dict = await request.json() + if body.get("method") != "ping": + raise HTTPException(status_code=400, detail="Invalid method") + req_id: str = body.get("id") + logger.debug(f"Authenticated user {user} sent ping request.") + # Return an empty result per the MCP ping specification. + response: dict = {"jsonrpc": "2.0", "id": req_id, "result": {}} + return JSONResponse(content=response) + except Exception as e: + error_response: dict = { + "jsonrpc": "2.0", + "id": body.get("id") if "body" in locals() else None, + "error": {"code": -32603, "message": "Internal error", "data": str(e)}, + } + return JSONResponse(status_code=500, content=error_response) + + +@protocol_router.post("/notifications") +async def handle_notification(request: Request, user: str = Depends(require_auth)) -> None: + """ + Handles incoming notifications from clients. Depending on the notification method, + different actions are taken (e.g., logging initialization, cancellation, or messages). + + Args: + request (Request): The incoming request containing the notification data. + user (str): The authenticated user making the request. + """ + body = await request.json() + logger.debug(f"User {user} sent a notification") + if body.get("method") == "notifications/initialized": + logger.info("Client initialized") + await logging_service.notify("Client initialized", LogLevel.INFO) + elif body.get("method") == "notifications/cancelled": + request_id = body.get("params", {}).get("requestId") + logger.info(f"Request cancelled: {request_id}") + await logging_service.notify(f"Request cancelled: {request_id}", LogLevel.INFO) + elif body.get("method") == "notifications/message": + params = body.get("params", {}) + await logging_service.notify( + params.get("data"), + LogLevel(params.get("level", "info")), + params.get("logger"), + ) + + +@protocol_router.post("/completion/complete") +async def handle_completion(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)): + """ + Handles the completion of tasks by processing a completion request. + + Args: + request (Request): The incoming request with completion data. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + The result of the completion process. + """ + body = await request.json() + logger.debug(f"User {user} sent a completion request") + return await completion_service.handle_completion(db, body) + + +@protocol_router.post("/sampling/createMessage") +async def handle_sampling(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)): + """ + Handles the creation of a new message for sampling. + + Args: + request (Request): The incoming request with sampling data. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + The result of the message creation process. + """ + logger.debug(f"User {user} sent a sampling request") + body = await request.json() + return await sampling_handler.create_message(db, body) + + +############### +# Server APIs # +############### +@server_router.get("", response_model=List[ServerRead]) +@server_router.get("/", response_model=List[ServerRead]) +async def list_servers( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ServerRead]: + """ + Lists all servers in the system, optionally including inactive ones. + + Args: + include_inactive (bool): Whether to include inactive servers in the response. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + List[ServerRead]: A list of server objects. + """ + logger.debug(f"User {user} requested server list") + return await server_service.list_servers(db, include_inactive=include_inactive) + + +@server_router.get("/{server_id}", response_model=ServerRead) +async def get_server(server_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> ServerRead: + """ + Retrieves a server by its ID. + + Args: + server_id (int): The ID of the server to retrieve. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + ServerRead: The server object with the specified ID. + + Raises: + HTTPException: If the server is not found. + """ + try: + logger.debug(f"User {user} requested server with ID {server_id}") + return await server_service.get_server(db, server_id) + except ServerNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + + +@server_router.post("/", response_model=ServerRead, status_code=201) +async def create_server( + server: ServerCreate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ServerRead: + """ + Creates a new server. + + Args: + server (ServerCreate): The data for the new server. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + ServerRead: The created server object. + + Raises: + HTTPException: If there is a conflict with the server name or other errors. + """ + try: + logger.debug(f"User {user} is creating a new server") + return await server_service.register_server(db, server) + except ServerNameConflictError as e: + raise HTTPException(status_code=409, detail=str(e)) + except ServerError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@server_router.put("/{server_id}", response_model=ServerRead) +async def update_server( + server_id: int, + server: ServerUpdate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ServerRead: + """ + Updates the information of an existing server. + + Args: + server_id (int): The ID of the server to update. + server (ServerUpdate): The updated server data. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + ServerRead: The updated server object. + + Raises: + HTTPException: If the server is not found, there is a name conflict, or other errors. + """ + try: + logger.debug(f"User {user} is updating server with ID {server_id}") + return await server_service.update_server(db, server_id, server) + except ServerNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + except ServerNameConflictError as e: + raise HTTPException(status_code=409, detail=str(e)) + except ServerError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@server_router.post("/{server_id}/toggle", response_model=ServerRead) +async def toggle_server_status( + server_id: int, + activate: bool = True, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ServerRead: + """ + Toggles the status of a server (activate or deactivate). + + Args: + server_id (int): The ID of the server to toggle. + activate (bool): Whether to activate or deactivate the server. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + ServerRead: The server object after the status change. + + Raises: + HTTPException: If the server is not found or there is an error. + """ + try: + logger.debug(f"User {user} is toggling server with ID {server_id} to {'active' if activate else 'inactive'}") + return await server_service.toggle_server_status(db, server_id, activate) + except ServerNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + except ServerError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@server_router.delete("/{server_id}", response_model=Dict[str, str]) +async def delete_server(server_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, str]: + """ + Deletes a server by its ID. + + Args: + server_id (int): The ID of the server to delete. + db (Session): The database session used to interact with the data store. + user (str): The authenticated user making the request. + + Returns: + Dict[str, str]: A success message indicating the server was deleted. + + Raises: + HTTPException: If the server is not found or there is an error. + """ + try: + logger.debug(f"User {user} is deleting server with ID {server_id}") + await server_service.delete_server(db, server_id) + return { + "status": "success", + "message": f"Server {server_id} deleted successfully", + } + except ServerNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + except ServerError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@server_router.get("/{server_id}/sse") +async def sse_endpoint(request: Request, server_id: int, user: str = Depends(require_auth)): + """ + Establishes a Server-Sent Events (SSE) connection for real-time updates about a server. + + Args: + request (Request): The incoming request. + server_id (int): The ID of the server for which updates are received. + user (str): The authenticated user making the request. + + Returns: + The SSE response object for the established connection. + + Raises: + HTTPException: If there is an error in establishing the SSE connection. + """ + try: + logger.debug(f"User {user} is establishing SSE connection for server {server_id}") + base_url = str(request.base_url).rstrip("/") + server_sse_url = f"{base_url}/servers/{server_id}" + transport = SSETransport(base_url=server_sse_url) + await transport.connect() + await session_registry.add_session(transport.session_id, transport) + response = await transport.create_sse_response(request) + + asyncio.create_task(session_registry.respond(server_id, user, session_id=transport.session_id)) + + tasks = BackgroundTasks() + tasks.add_task(session_registry.remove_session, transport.session_id) + response.background = tasks + logger.info(f"SSE connection established: {transport.session_id}") + return response + except Exception as e: + logger.error(f"SSE connection error: {e}") + raise HTTPException(status_code=500, detail="SSE connection failed") + + +@server_router.post("/{server_id}/message") +async def message_endpoint(request: Request, server_id: int, user: str = Depends(require_auth)): + """ + Handles incoming messages for a specific server. + + Args: + request (Request): The incoming message request. + server_id (int): The ID of the server receiving the message. + user (str): The authenticated user making the request. + + Returns: + JSONResponse: A success status after processing the message. + + Raises: + HTTPException: If there are errors processing the message. + """ + try: + logger.debug(f"User {user} sent a message to server {server_id}") + session_id = request.query_params.get("session_id") + if not session_id: + logger.error("Missing session_id in message request") + raise HTTPException(status_code=400, detail="Missing session_id") + + message = await request.json() + + await session_registry.broadcast( + session_id=session_id, + message=message, + ) + + return JSONResponse(content={"status": "success"}, status_code=202) + except ValueError as e: + logger.error(f"Invalid message format: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except HTTPException: + raise + except Exception as e: + logger.error(f"Message handling error: {e}") + raise HTTPException(status_code=500, detail="Failed to process message") + + +@server_router.get("/{server_id}/tools", response_model=List[ToolRead]) +async def server_get_tools( + server_id: int, + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ToolRead]: + """ + List tools for the server with an option to include inactive tools. + + This endpoint retrieves a list of tools from the database, optionally including + those that are inactive. The inactive filter helps administrators manage tools + that have been deactivated but not deleted from the system. + + Args: + server_id (int): ID of the server + include_inactive (bool): Whether to include inactive tools in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[ToolRead]: A list of tool records formatted with by_alias=True. + """ + logger.debug(f"User: {user} has listed tools for the server_id: {server_id}") + tools = await tool_service.list_server_tools(db, server_id=server_id, include_inactive=include_inactive) + return [tool.dict(by_alias=True) for tool in tools] + + +@server_router.get("/{server_id}/resources", response_model=List[ResourceRead]) +async def server_get_resources( + server_id: int, + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ResourceRead]: + """ + List resources for the server with an option to include inactive resources. + + This endpoint retrieves a list of resources from the database, optionally including + those that are inactive. The inactive filter is useful for administrators who need + to view or manage resources that have been deactivated but not deleted. + + Args: + server_id (int): ID of the server + include_inactive (bool): Whether to include inactive resources in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[ResourceRead]: A list of resource records formatted with by_alias=True. + """ + logger.debug(f"User: {user} has listed resources for the server_id: {server_id}") + resources = await resource_service.list_server_resources(db, server_id=server_id, include_inactive=include_inactive) + return [resource.dict(by_alias=True) for resource in resources] + + +@server_router.get("/{server_id}/prompts", response_model=List[PromptRead]) +async def server_get_prompts( + server_id: int, + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[PromptRead]: + """ + List prompts for the server with an option to include inactive prompts. + + This endpoint retrieves a list of prompts from the database, optionally including + those that are inactive. The inactive filter helps administrators see and manage + prompts that have been deactivated but not deleted from the system. + + Args: + server_id (int): ID of the server + include_inactive (bool): Whether to include inactive prompts in the results. + db (Session): Database session dependency. + user (str): Authenticated user dependency. + + Returns: + List[PromptRead]: A list of prompt records formatted with by_alias=True. + """ + logger.debug(f"User: {user} has listed prompts for the server_id: {server_id}") + prompts = await prompt_service.list_server_prompts(db, server_id=server_id, include_inactive=include_inactive) + return [prompt.dict(by_alias=True) for prompt in prompts] + + +############# +# Tool APIs # +############# +@tool_router.get("", response_model=Union[List[ToolRead], List[Dict], Dict, List]) +@tool_router.get("/", response_model=Union[List[ToolRead], List[Dict], Dict, List]) +async def list_tools( + cursor: Optional[str] = None, # Add this parameter + include_inactive: bool = False, + db: Session = Depends(get_db), + apijsonpath: JsonPathModifier = Body(None), + _: str = Depends(require_auth), +) -> Union[List[ToolRead], List[Dict], Dict]: + """List all registered tools with pagination support. + + Args: + cursor: Pagination cursor for fetching the next set of results + include_inactive: Whether to include inactive tools in the results + db: Database session + apijsonpath: JSON path modifier to filter or transform the response + _: Authenticated user + + Returns: + List of tools or modified result based on jsonpath + """ + + # For now just pass the cursor parameter even if not used + data = await tool_service.list_tools(db, cursor=cursor, include_inactive=include_inactive) + + if apijsonpath is None: + return data + + tools_dict_list = [tool.to_dict(use_alias=True) for tool in data] + + return jsonpath_modifier(tools_dict_list, apijsonpath.jsonpath, apijsonpath.mapping) + + +@tool_router.post("", response_model=ToolRead) +@tool_router.post("/", response_model=ToolRead) +async def create_tool(tool: ToolCreate, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> ToolRead: + """ + Creates a new tool in the system. + + Args: + tool (ToolCreate): The data needed to create the tool. + db (Session): The database session dependency. + user (str): The authenticated user making the request. + + Returns: + ToolRead: The created tool data. + + Raises: + HTTPException: If the tool name already exists or other validation errors occur. + """ + try: + logger.debug(f"User {user} is creating a new tool") + return await tool_service.register_tool(db, tool) + except ToolNameConflictError as e: + if not e.is_active and e.tool_id: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Tool name already exists but is inactive. Consider activating it with ID: {e.tool_id}", + ) + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + except ToolError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@tool_router.get("/{tool_id}", response_model=Union[ToolRead, Dict]) +async def get_tool( + tool_id: int, + db: Session = Depends(get_db), + user: str = Depends(require_auth), + apijsonpath: JsonPathModifier = Body(None), +) -> Union[ToolRead, Dict]: + """ + Retrieve a tool by ID, optionally applying a JSONPath post-filter. + + Args: + tool_id: The numeric ID of the tool. + db: Active SQLAlchemy session (dependency). + user: Authenticated username (dependency). + apijsonpath: Optional JSON-Path modifier supplied in the body. + + Returns: + The raw ``ToolRead`` model **or** a JSON-transformed ``dict`` if + a JSONPath filter/mapping was supplied. + + Raises: + HTTPException: If the tool does not exist or the transformation fails. + """ + try: + logger.debug(f"User {user} is retrieving tool with ID {tool_id}") + data = await tool_service.get_tool(db, tool_id) + if apijsonpath is None: + return data + + data_dict = data.to_dict(use_alias=True) + + return jsonpath_modifier(data_dict, apijsonpath.jsonpath, apijsonpath.mapping) + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + + +@tool_router.put("/{tool_id}", response_model=ToolRead) +async def update_tool( + tool_id: int, + tool: ToolUpdate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ToolRead: + """ + Updates an existing tool with new data. + + Args: + tool_id (int): The ID of the tool to update. + tool (ToolUpdate): The updated tool information. + db (Session): The database session dependency. + user (str): The authenticated user making the request. + + Returns: + ToolRead: The updated tool data. + + Raises: + HTTPException: If an error occurs during the update. + """ + try: + logger.debug(f"User {user} is updating tool with ID {tool_id}") + return await tool_service.update_tool(db, tool_id, tool) + except Exception as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@tool_router.delete("/{tool_id}") +async def delete_tool(tool_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, str]: + """ + Permanently deletes a tool by ID. + + Args: + tool_id (int): The ID of the tool to delete. + db (Session): The database session dependency. + user (str): The authenticated user making the request. + + Returns: + Dict[str, str]: A confirmation message upon successful deletion. + + Raises: + HTTPException: If an error occurs during deletion. + """ + try: + logger.debug(f"User {user} is deleting tool with ID {tool_id}") + await tool_service.delete_tool(db, tool_id) + return {"status": "success", "message": f"Tool {tool_id} permanently deleted"} + except Exception as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@tool_router.post("/{tool_id}/toggle") +async def toggle_tool_status( + tool_id: int, + activate: bool = True, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> Dict[str, Any]: + """ + Activates or deactivates a tool. + + Args: + tool_id (int): The ID of the tool to toggle. + activate (bool): Whether to activate (`True`) or deactivate (`False`) the tool. + db (Session): The database session dependency. + user (str): The authenticated user making the request. + + Returns: + Dict[str, Any]: The status, message, and updated tool data. + + Raises: + HTTPException: If an error occurs during status toggling. + """ + try: + logger.debug(f"User {user} is toggling tool with ID {tool_id} to {'active' if activate else 'inactive'}") + tool = await tool_service.toggle_tool_status(db, tool_id, activate) + return { + "status": "success", + "message": f"Tool {tool_id} {'activated' if activate else 'deactivated'}", + "tool": tool.model_dump(), + } + except Exception as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +################# +# Resource APIs # +################# +# --- Resource templates endpoint - MUST come before variable paths --- +@resource_router.get("/templates/list", response_model=ListResourceTemplatesResult) +async def list_resource_templates( + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ListResourceTemplatesResult: + """ + List all available resource templates. + + Args: + db (Session): Database session. + user (str): Authenticated user. + + Returns: + ListResourceTemplatesResult: A paginated list of resource templates. + """ + logger.debug(f"User {user} requested resource templates") + resource_templates = await resource_service.list_resource_templates(db) + # For simplicity, we're not implementing real pagination here + return ListResourceTemplatesResult(_meta={}, resource_templates=resource_templates, next_cursor=None) # No pagination for now + + +@resource_router.post("/{resource_id}/toggle") +async def toggle_resource_status( + resource_id: int, + activate: bool = True, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> Dict[str, Any]: + """ + Activate or deactivate a resource by its ID. + + Args: + resource_id (int): The ID of the resource. + activate (bool): True to activate, False to deactivate. + db (Session): Database session. + user (str): Authenticated user. + + Returns: + Dict[str, Any]: Status message and updated resource data. + + Raises: + HTTPException: If toggling fails. + """ + logger.debug(f"User {user} is toggling resource with ID {resource_id} to {'active' if activate else 'inactive'}") + try: + resource = await resource_service.toggle_resource_status(db, resource_id, activate) + return { + "status": "success", + "message": f"Resource {resource_id} {'activated' if activate else 'deactivated'}", + "resource": resource.model_dump(), + } + except Exception as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@resource_router.get("", response_model=List[ResourceRead]) +@resource_router.get("/", response_model=List[ResourceRead]) +async def list_resources( + cursor: Optional[str] = None, # Add this parameter + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[ResourceRead]: + """ + Retrieve a list of resources. + + Args: + cursor (Optional[str]): Optional cursor for pagination. + include_inactive (bool): Whether to include inactive resources. + db (Session): Database session. + user (str): Authenticated user. + + Returns: + List[ResourceRead]: List of resources. + """ + logger.debug(f"User {user} requested resource list with cursor {cursor} and include_inactive={include_inactive}") + if cached := resource_cache.get("resource_list"): + return cached + # Pass the cursor parameter + resources = await resource_service.list_resources(db, include_inactive=include_inactive) + resource_cache.set("resource_list", resources) + return resources + + +@resource_router.post("", response_model=ResourceRead) +@resource_router.post("/", response_model=ResourceRead) +async def create_resource( + resource: ResourceCreate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ResourceRead: + """ + Create a new resource. + + Args: + resource (ResourceCreate): Data for the new resource. + db (Session): Database session. + user (str): Authenticated user. + + Returns: + ResourceRead: The created resource. + + Raises: + HTTPException: On conflict or validation errors. + """ + logger.debug(f"User {user} is creating a new resource") + try: + result = await resource_service.register_resource(db, resource) + return result + except ResourceURIConflictError as e: + raise HTTPException(status_code=409, detail=str(e)) + except ResourceError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@resource_router.get("/{uri:path}") +async def read_resource(uri: str, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> ResourceContent: + """ + Read a resource by its URI. + + Args: + uri (str): URI of the resource. + db (Session): Database session. + user (str): Authenticated user. + + Returns: + ResourceContent: The content of the resource. + + Raises: + HTTPException: If the resource cannot be found or read. + """ + logger.debug(f"User {user} requested resource with URI {uri}") + if cached := resource_cache.get(uri): + return cached + try: + content: ResourceContent = await resource_service.read_resource(db, uri) + except (ResourceNotFoundError, ResourceError) as exc: + # Translate to FastAPI HTTP error + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + resource_cache.set(uri, content) + return content + + +@resource_router.put("/{uri:path}", response_model=ResourceRead) +async def update_resource( + uri: str, + resource: ResourceUpdate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> ResourceRead: + """ + Update a resource identified by its URI. + + Args: + uri (str): URI of the resource. + resource (ResourceUpdate): New resource data. + db (Session): Database session. + user (str): Authenticated user. + + Returns: + ResourceRead: The updated resource. + + Raises: + HTTPException: If the resource is not found or update fails. + """ + try: + logger.debug(f"User {user} is updating resource with URI {uri}") + result = await resource_service.update_resource(db, uri, resource) + except ResourceNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + await invalidate_resource_cache(uri) + return result + + +@resource_router.delete("/{uri:path}") +async def delete_resource(uri: str, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, str]: + """ + Delete a resource by its URI. + + Args: + uri (str): URI of the resource to delete. + db (Session): Database session. + user (str): Authenticated user. + + Returns: + Dict[str, str]: Status message indicating deletion success. + + Raises: + HTTPException: If the resource is not found or deletion fails. + """ + try: + logger.debug(f"User {user} is deleting resource with URI {uri}") + await resource_service.delete_resource(db, uri) + await invalidate_resource_cache(uri) + return {"status": "success", "message": f"Resource {uri} deleted"} + except ResourceNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + except ResourceError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@resource_router.post("/subscribe/{uri:path}") +async def subscribe_resource(uri: str, user: str = Depends(require_auth)) -> StreamingResponse: + """ + Subscribe to server-sent events (SSE) for a specific resource. + + Args: + uri (str): URI of the resource to subscribe to. + user (str): Authenticated user. + + Returns: + StreamingResponse: A streaming response with event updates. + """ + logger.debug(f"User {user} is subscribing to resource with URI {uri}") + return StreamingResponse(resource_service.subscribe_events(uri), media_type="text/event-stream") + + +############### +# Prompt APIs # +############### +@prompt_router.post("/{prompt_id}/toggle") +async def toggle_prompt_status( + prompt_id: int, + activate: bool = True, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> Dict[str, Any]: + """ + Toggle the activation status of a prompt. + + Args: + prompt_id: ID of the prompt to toggle. + activate: True to activate, False to deactivate. + db: Database session. + user: Authenticated user. + + Returns: + Status message and updated prompt details. + + Raises: + HTTPException: If the toggle fails (e.g., prompt not found or database error); emitted with *400 Bad Request* status and an error message. + """ + logger.debug(f"User: {user} requested toggle for prompt {prompt_id}, activate={activate}") + try: + prompt = await prompt_service.toggle_prompt_status(db, prompt_id, activate) + return { + "status": "success", + "message": f"Prompt {prompt_id} {'activated' if activate else 'deactivated'}", + "prompt": prompt.model_dump(), + } + except Exception as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@prompt_router.get("", response_model=List[PromptRead]) +@prompt_router.get("/", response_model=List[PromptRead]) +async def list_prompts( + cursor: Optional[str] = None, + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[PromptRead]: + """ + List prompts with optional pagination and inclusion of inactive items. + + Args: + cursor: Cursor for pagination. + include_inactive: Include inactive prompts. + db: Database session. + user: Authenticated user. + + Returns: + List of prompt records. + """ + logger.debug(f"User: {user} requested prompt list with include_inactive={include_inactive}, cursor={cursor}") + return await prompt_service.list_prompts(db, cursor=cursor, include_inactive=include_inactive) + + +@prompt_router.post("/", response_model=PromptRead) +async def create_prompt( + prompt: PromptCreate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> PromptRead: + """ + Create a new prompt. + + Args: + prompt (PromptCreate): Payload describing the prompt to create. + db (Session): Active SQLAlchemy session. + user (str): Authenticated username. + + Returns: + PromptRead: The newly–created prompt. + + Raises: + HTTPException: * **409 Conflict** – another prompt with the same name already exists. + * **400 Bad Request** – validation or persistence error raised + by :pyclass:`~mcpgateway.services.prompt_service.PromptService`. + """ + logger.debug(f"User: {user} requested to create prompt: {prompt}") + try: + return await prompt_service.register_prompt(db, prompt) + except PromptNameConflictError as e: + raise HTTPException(status_code=409, detail=str(e)) + except PromptError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@prompt_router.post("/{name}") +async def get_prompt( + name: str, + args: Dict[str, str] = Body({}), + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> Any: + """Get a prompt by name with arguments. + + This implements the prompts/get functionality from the MCP spec, + which requires a POST request with arguments in the body. + + + Args: + name: Name of the prompt. + args: Template arguments. + db: Database session. + user: Authenticated user. + + Returns: + Rendered prompt or metadata. + """ + logger.debug(f"User: {user} requested prompt: {name} with args={args}") + return await prompt_service.get_prompt(db, name, args) + + +@prompt_router.get("/{name}") +async def get_prompt_no_args( + name: str, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> Any: + """Get a prompt by name without arguments. + + This endpoint is for convenience when no arguments are needed. + + Args: + name: The name of the prompt to retrieve + db: Database session + user: Authenticated user + + Returns: + The prompt template information + """ + logger.debug(f"User: {user} requested prompt: {name} with no arguments") + return await prompt_service.get_prompt(db, name, {}) + + +@prompt_router.put("/{name}", response_model=PromptRead) +async def update_prompt( + name: str, + prompt: PromptUpdate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> PromptRead: + """ + Update (overwrite) an existing prompt definition. + + Args: + name (str): Identifier of the prompt to update. + prompt (PromptUpdate): New prompt content and metadata. + db (Session): Active SQLAlchemy session. + user (str): Authenticated username. + + Returns: + PromptRead: The updated prompt object. + + Raises: + HTTPException: * **409 Conflict** – a different prompt with the same *name* already exists and is still active. + * **400 Bad Request** – validation or persistence error raised by :pyclass:`~mcpgateway.services.prompt_service.PromptService`. + """ + logger.debug(f"User: {user} requested to update prompt: {name} with data={prompt}") + try: + return await prompt_service.update_prompt(db, name, prompt) + except PromptNameConflictError as e: + raise HTTPException(status_code=409, detail=str(e)) + except PromptError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@prompt_router.delete("/{name}") +async def delete_prompt(name: str, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, str]: + """ + Delete a prompt by name. + + Args: + name: Name of the prompt. + db: Database session. + user: Authenticated user. + + Returns: + Status message. + """ + logger.debug(f"User: {user} requested deletion of prompt {name}") + try: + await prompt_service.delete_prompt(db, name) + return {"status": "success", "message": f"Prompt {name} deleted"} + except PromptNotFoundError as e: + return {"status": "error", "message": str(e)} + except PromptError as e: + return {"status": "error", "message": str(e)} + + +################ +# Gateway APIs # +################ +@gateway_router.post("/{gateway_id}/toggle") +async def toggle_gateway_status( + gateway_id: int, + activate: bool = True, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> Dict[str, Any]: + """ + Toggle the activation status of a gateway. + + Args: + gateway_id (int): Numeric ID of the gateway to toggle. + activate (bool): ``True`` to activate, ``False`` to deactivate. + db (Session): Active SQLAlchemy session. + user (str): Authenticated username. + + Returns: + Dict[str, Any]: A dict containing the operation status, a message, and the updated gateway object. + + Raises: + HTTPException: Returned with **400 Bad Request** if the toggle operation fails (e.g., the gateway does not exist or the database raises an unexpected error). + """ + logger.debug(f"User '{user}' requested toggle for gateway {gateway_id}, activate={activate}") + try: + gateway = await gateway_service.toggle_gateway_status( + db, + gateway_id, + activate, + ) + return { + "status": "success", + "message": f"Gateway {gateway_id} {'activated' if activate else 'deactivated'}", + "gateway": gateway.model_dump(), + } + except Exception as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@gateway_router.get("", response_model=List[GatewayRead]) +@gateway_router.get("/", response_model=List[GatewayRead]) +async def list_gateways( + include_inactive: bool = False, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> List[GatewayRead]: + """ + List all gateways. + + Args: + include_inactive: Include inactive gateways. + db: Database session. + user: Authenticated user. + + Returns: + List of gateway records. + """ + logger.debug(f"User '{user}' requested list of gateways with include_inactive={include_inactive}") + return await gateway_service.list_gateways(db, include_inactive=include_inactive) + + +@gateway_router.post("", response_model=GatewayRead) +@gateway_router.post("/", response_model=GatewayRead) +async def register_gateway( + gateway: GatewayCreate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> GatewayRead: + """ + Register a new gateway. + + Args: + gateway: Gateway creation data. + db: Database session. + user: Authenticated user. + + Returns: + Created gateway. + """ + logger.debug(f"User '{user}' requested to register gateway: {gateway}") + return await gateway_service.register_gateway(db, gateway) + + +@gateway_router.get("/{gateway_id}", response_model=GatewayRead) +async def get_gateway(gateway_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> GatewayRead: + """ + Retrieve a gateway by ID. + + Args: + gateway_id: ID of the gateway. + db: Database session. + user: Authenticated user. + + Returns: + Gateway data. + """ + logger.debug(f"User '{user}' requested gateway {gateway_id}") + return await gateway_service.get_gateway(db, gateway_id) + + +@gateway_router.put("/{gateway_id}", response_model=GatewayRead) +async def update_gateway( + gateway_id: int, + gateway: GatewayUpdate, + db: Session = Depends(get_db), + user: str = Depends(require_auth), +) -> GatewayRead: + """ + Update a gateway. + + Args: + gateway_id: Gateway ID. + gateway: Gateway update data. + db: Database session. + user: Authenticated user. + + Returns: + Updated gateway. + """ + logger.debug(f"User '{user}' requested update on gateway {gateway_id} with data={gateway}") + return await gateway_service.update_gateway(db, gateway_id, gateway) + + +@gateway_router.delete("/{gateway_id}") +async def delete_gateway(gateway_id: int, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> Dict[str, str]: + """ + Delete a gateway by ID. + + Args: + gateway_id: ID of the gateway. + db: Database session. + user: Authenticated user. + + Returns: + Status message. + """ + logger.debug(f"User '{user}' requested deletion of gateway {gateway_id}") + await gateway_service.delete_gateway(db, gateway_id) + return {"status": "success", "message": f"Gateway {gateway_id} deleted"} + + +############## +# Root APIs # +############## +@root_router.get("", response_model=List[Root]) +@root_router.get("/", response_model=List[Root]) +async def list_roots( + user: str = Depends(require_auth), +) -> List[Root]: + """ + Retrieve a list of all registered roots. + + Args: + user: Authenticated user. + + Returns: + List of Root objects. + """ + logger.debug(f"User '{user}' requested list of roots") + return await root_service.list_roots() + + +@root_router.post("/", response_model=Root) +async def add_root( + root: Root, # Accept JSON body using the Root model from types.py + user: str = Depends(require_auth), +) -> Root: + """ + Add a new root. + + Args: + root: Root object containing URI and name. + user: Authenticated user. + + Returns: + The added Root object. + """ + logger.debug(f"User '{user}' requested to add root: {root}") + return await root_service.add_root(str(root.uri), root.name) + + +@root_router.delete("/{uri:path}") +async def remove_root( + uri: str, + user: str = Depends(require_auth), +) -> Dict[str, str]: + """ + Remove a registered root by URI. + + Args: + uri: URI of the root to remove. + user: Authenticated user. + + Returns: + Status message indicating result. + """ + logger.debug(f"User '{user}' requested to remove root with URI: {uri}") + await root_service.remove_root(uri) + return {"status": "success", "message": f"Root {uri} removed"} + + +@root_router.get("/changes") +async def subscribe_roots_changes( + user: str = Depends(require_auth), +) -> StreamingResponse: + """ + Subscribe to real-time changes in root list via Server-Sent Events (SSE). + + Args: + user: Authenticated user. + + Returns: + StreamingResponse with event-stream media type. + """ + logger.debug(f"User '{user}' subscribed to root changes stream") + return StreamingResponse(root_service.subscribe_changes(), media_type="text/event-stream") + + +################## +# Utility Routes # +################## +@utility_router.post("/rpc/") +@utility_router.post("/rpc") +async def handle_rpc(request: Request, db: Session = Depends(get_db), user: str = Depends(require_auth)): # revert this back + """Handle RPC requests. + + Args: + request (Request): The incoming FastAPI request. + db (Session): Database session. + user (str): The authenticated user. + + Returns: + Response with the RPC result or error. + """ + try: + logger.debug(f"User {user} made an RPC request") + body = await request.json() + validate_request(body) + method = body["method"] + # rpc_id = body.get("id") + params = body.get("params", {}) + cursor = params.get("cursor") # Extract cursor parameter + + if method == "tools/list": + tools = await tool_service.list_tools(db, cursor=cursor) + result = [t.model_dump(by_alias=True, exclude_none=True) for t in tools] + elif method == "list_tools": # Legacy endpoint + tools = await tool_service.list_tools(db, cursor=cursor) + result = [t.model_dump(by_alias=True, exclude_none=True) for t in tools] + elif method == "initialize": + result = initialize( + InitializeRequest( + protocol_version=params.get("protocolVersion") or params.get("protocol_version", ""), + capabilities=params.get("capabilities", {}), + client_info=params.get("clientInfo") or params.get("client_info", {}), + ), + user, + ).model_dump(by_alias=True, exclude_none=True) + elif method == "list_gateways": + gateways = await gateway_service.list_gateways(db, include_inactive=False) + result = [g.model_dump(by_alias=True, exclude_none=True) for g in gateways] + elif method == "list_roots": + roots = await root_service.list_roots() + result = [r.model_dump(by_alias=True, exclude_none=True) for r in roots] + elif method == "resources/list": + resources = await resource_service.list_resources(db) + result = [r.model_dump(by_alias=True, exclude_none=True) for r in resources] + elif method == "prompts/list": + prompts = await prompt_service.list_prompts(db, cursor=cursor) + result = [p.model_dump(by_alias=True, exclude_none=True) for p in prompts] + elif method == "prompts/get": + name = params.get("name") + arguments = params.get("arguments", {}) + if not name: + raise JSONRPCError(-32602, "Missing prompt name in parameters", params) + result = await prompt_service.get_prompt(db, name, arguments) + if hasattr(result, "model_dump"): + result = result.model_dump(by_alias=True, exclude_none=True) + elif method == "ping": + # Per the MCP spec, a ping returns an empty result. + result = {} + else: + try: + result = await tool_service.invoke_tool(db, method, params) + if hasattr(result, "model_dump"): + result = result.model_dump(by_alias=True, exclude_none=True) + except ValueError: + result = await gateway_service.forward_request(db, method, params) + if hasattr(result, "model_dump"): + result = result.model_dump(by_alias=True, exclude_none=True) + + response = result + return response + + except JSONRPCError as e: + return e.to_dict() + except Exception as e: + logger.error(f"RPC error: {str(e)}") + return { + "jsonrpc": "2.0", + "error": {"code": -32000, "message": "Internal error", "data": str(e)}, + "id": body.get("id") if "body" in locals() else None, + } + + +@utility_router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + """ + Handle WebSocket connection to relay JSON-RPC requests to the internal RPC endpoint. + + Accepts incoming text messages, parses them as JSON-RPC requests, sends them to /rpc, + and returns the result to the client over the same WebSocket. + + Args: + websocket: The WebSocket connection instance. + """ + try: + await websocket.accept() + while True: + try: + data = await websocket.receive_text() + async with httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) as client: + response = await client.post( + f"http://localhost:{settings.port}/rpc", + json=json.loads(data), + headers={"Content-Type": "application/json"}, + ) + await websocket.send_text(response.text) + except JSONRPCError as e: + await websocket.send_text(json.dumps(e.to_dict())) + except json.JSONDecodeError: + await websocket.send_text( + json.dumps( + { + "jsonrpc": "2.0", + "error": {"code": -32700, "message": "Parse error"}, + "id": None, + } + ) + ) + except Exception as e: + logger.error(f"WebSocket error: {str(e)}") + await websocket.close(code=1011) + break + except WebSocketDisconnect: + logger.info("WebSocket disconnected") + except Exception as e: + logger.error(f"WebSocket connection error: {str(e)}") + try: + await websocket.close(code=1011) + except Exception as er: + logger.error(f"Error while closing WebSocket: {er}") + + +@utility_router.get("/sse") +async def utility_sse_endpoint(request: Request, user: str = Depends(require_auth)): + """ + Establish a Server-Sent Events (SSE) connection for real-time updates. + + Args: + request (Request): The incoming HTTP request. + user (str): Authenticated username. + + Returns: + StreamingResponse: A streaming response that keeps the connection + open and pushes events to the client. + + Raises: + HTTPException: Returned with **500 Internal Server Error** if the SSE connection cannot be established or an unexpected error occurs while creating the transport. + """ + try: + logger.debug("User %s requested SSE connection", user) + base_url = str(request.base_url).rstrip("/") + transport = SSETransport(base_url=base_url) + await transport.connect() + await session_registry.add_session(transport.session_id, transport) + + asyncio.create_task(session_registry.respond(None, user, session_id=transport.session_id)) + + response = await transport.create_sse_response(request) + tasks = BackgroundTasks() + tasks.add_task(session_registry.remove_session, transport.session_id) + response.background = tasks + logger.info("SSE connection established: %s", transport.session_id) + return response + except Exception as e: + logger.error("SSE connection error: %s", e) + raise HTTPException(status_code=500, detail="SSE connection failed") + + +@utility_router.post("/message") +async def utility_message_endpoint(request: Request, user: str = Depends(require_auth)): + """ + Handle a JSON-RPC message directed to a specific SSE session. + + Args: + request (Request): Incoming request containing the JSON-RPC payload. + user (str): Authenticated user. + + Returns: + JSONResponse: ``{"status": "success"}`` with HTTP 202 on success. + + Raises: + HTTPException: * **400 Bad Request** – ``session_id`` query parameter is missing or the payload cannot be parsed as JSON. + * **500 Internal Server Error** – An unexpected error occurs while broadcasting the message. + """ + try: + logger.debug("User %s sent a message to SSE session", user) + + session_id = request.query_params.get("session_id") + if not session_id: + logger.error("Missing session_id in message request") + raise HTTPException(status_code=400, detail="Missing session_id") + + message = await request.json() + + await session_registry.broadcast( + session_id=session_id, + message=message, + ) + + return JSONResponse(content={"status": "success"}, status_code=202) + + except ValueError as e: + logger.error("Invalid message format: %s", e) + raise HTTPException(status_code=400, detail=str(e)) + except HTTPException: + raise + except Exception as exc: + logger.error("Message handling error: %s", exc) + raise HTTPException(status_code=500, detail="Failed to process message") + + +@utility_router.post("/logging/setLevel") +async def set_log_level(request: Request, user: str = Depends(require_auth)) -> None: + """ + Update the server's log level at runtime. + + Args: + request: HTTP request with log level JSON body. + user: Authenticated user. + + Returns: + None + """ + logger.debug(f"User {user} requested to set log level") + body = await request.json() + level = LogLevel(body["level"]) + await logging_service.set_level(level) + return None + + +#################### +# Metrics # +#################### +@metrics_router.get("", response_model=dict) +async def get_metrics(db: Session = Depends(get_db), user: str = Depends(require_auth)) -> dict: + """ + Retrieve aggregated metrics for all entity types (Tools, Resources, Servers, Prompts). + + Args: + db: Database session + user: Authenticated user + + Returns: + A dictionary with keys for each entity type and their aggregated metrics. + """ + logger.debug(f"User {user} requested aggregated metrics") + tool_metrics = await tool_service.aggregate_metrics(db) + resource_metrics = await resource_service.aggregate_metrics(db) + server_metrics = await server_service.aggregate_metrics(db) + prompt_metrics = await prompt_service.aggregate_metrics(db) + return { + "tools": tool_metrics, + "resources": resource_metrics, + "servers": server_metrics, + "prompts": prompt_metrics, + } + + +@metrics_router.post("/reset", response_model=dict) +async def reset_metrics(entity: Optional[str] = None, entity_id: Optional[int] = None, db: Session = Depends(get_db), user: str = Depends(require_auth)) -> dict: + """ + Reset metrics for a specific entity type and optionally a specific entity ID, + or perform a global reset if no entity is specified. + + Args: + entity: One of "tool", "resource", "server", "prompt", or None for global reset. + entity_id: Specific entity ID to reset metrics for (optional). + db: Database session + user: Authenticated user + + Returns: + A success message in a dictionary. + + Raises: + HTTPException: If an invalid entity type is specified. + """ + logger.debug(f"User {user} requested metrics reset for entity: {entity}, id: {entity_id}") + if entity is None: + # Global reset + await tool_service.reset_metrics(db) + await resource_service.reset_metrics(db) + await server_service.reset_metrics(db) + await prompt_service.reset_metrics(db) + elif entity.lower() == "tool": + await tool_service.reset_metrics(db, entity_id) + elif entity.lower() == "resource": + await resource_service.reset_metrics(db) + elif entity.lower() == "server": + await server_service.reset_metrics(db) + elif entity.lower() == "prompt": + await prompt_service.reset_metrics(db) + else: + raise HTTPException(status_code=400, detail="Invalid entity type for metrics reset") + return {"status": "success", "message": f"Metrics reset for {entity if entity else 'all entities'}"} + + +#################### +# Healthcheck # +#################### +@app.get("/health") +async def healthcheck(db: Session = Depends(get_db)): + """ + Perform a basic health check to verify database connectivity. + + Args: + db: SQLAlchemy session dependency. + + Returns: + A dictionary with the health status and optional error message. + """ + try: + # Execute the query using text() for an explicit textual SQL expression. + db.execute(text("SELECT 1")) + except Exception as e: + error_message = f"Database connection error: {str(e)}" + logger.error(error_message) + return {"status": "unhealthy", "error": error_message} + return {"status": "healthy"} + + +#################### +# Startup/Shutdown # +#################### +@asynccontextmanager +async def lifespan() -> AsyncIterator[None]: + """ + Manage the application's startup and shutdown lifecycle. + + The function initialises every core service on entry and then + shuts them down in reverse order on exit. + + Yields: + None + + Raises: + Exception: Any unhandled error that occurs during service + initialisation or shutdown is re-raised to the caller. + """ + logger.info("Starting MCP Gateway services") + try: + await tool_service.initialize() + await resource_service.initialize() + await prompt_service.initialize() + await gateway_service.initialize() + await root_service.initialize() + await completion_service.initialize() + await logging_service.initialize() + await sampling_handler.initialize() + await resource_cache.initialize() + logger.info("All services initialized successfully") + yield + except Exception as e: + logger.error(f"Error during startup: {str(e)}") + raise + finally: + logger.info("Shutting down MCP Gateway services") + for service in [ + resource_cache, + sampling_handler, + logging_service, + completion_service, + root_service, + gateway_service, + prompt_service, + resource_service, + tool_service, + ]: + try: + await service.shutdown() + except Exception as e: + logger.error(f"Error shutting down {service.__class__.__name__}: {str(e)}") + logger.info("Shutdown complete") + + +# Mount static files +app.mount("/static", StaticFiles(directory=str(settings.static_dir)), name="static") + +# Include routers +app.include_router(protocol_router) +app.include_router(tool_router) +app.include_router(resource_router) +app.include_router(prompt_router) +app.include_router(gateway_router) +app.include_router(root_router) +app.include_router(utility_router) +app.include_router(server_router) +app.include_router(metrics_router) + + +# Feature flags for admin UI and API +UI_ENABLED = settings.mcpgateway_ui_enabled +ADMIN_API_ENABLED = settings.mcpgateway_admin_api_enabled +logger.info(f"Admin UI enabled: {UI_ENABLED}") +logger.info(f"Admin API enabled: {ADMIN_API_ENABLED}") + +# Conditional UI and admin API handling +if ADMIN_API_ENABLED: + logger.info("Including admin_router - Admin API enabled") + app.include_router(admin_router) # Admin routes imported from admin.py +else: + logger.warning("Admin API routes not mounted - Admin API disabled via MCPGATEWAY_ADMIN_API_ENABLED=False") + +# Conditional static files mounting and root redirect +if UI_ENABLED: + # Mount static files for UI + logger.info("Mounting static files - UI enabled") + app.mount("/static", StaticFiles(directory=str(settings.static_dir)), name="static") + + # Redirect root path to admin UI + @app.get("/") + async def root_redirect(request: Request): + """ + Redirects the root path ("/") to "/admin". + + Logs a debug message before redirecting. + + Args: + request (Request): The incoming HTTP request (used only to build the + target URL via :pymeth:`starlette.requests.Request.url_for`). + + Returns: + RedirectResponse: Redirects to /admin. + """ + logger.debug("Redirecting root path to /admin") + return RedirectResponse(request.url_for("admin_home")) + +else: + # If UI is disabled, provide API info at root + logger.warning("Static files not mounted - UI disabled via MCPGATEWAY_UI_ENABLED=False") + + @app.get("/") + async def root_info(): + """ + Returns basic API information at the root path. + + Logs an info message indicating UI is disabled and provides details + about the app, including its name, version, and whether the UI and + admin API are enabled. + + Returns: + dict: API info with app name, version, and UI/admin API status. + """ + logger.info("UI disabled, serving API info at root path") + return {"name": settings.app_name, "version": "1.0.0", "description": f"{settings.app_name} API - UI is disabled", "ui_enabled": False, "admin_api_enabled": ADMIN_API_ENABLED} + + +# Expose some endpoints at the root level as well +app.post("/initialize")(initialize) +app.post("/notifications")(handle_notification) diff --git a/mcpgateway/schemas.py b/mcpgateway/schemas.py new file mode 100644 index 0000000000..87c675dba2 --- /dev/null +++ b/mcpgateway/schemas.py @@ -0,0 +1,1210 @@ +# -*- coding: utf-8 -*- +"""MCP Gateway Schema Definitions. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module provides Pydantic models for request/response validation in the MCP Gateway. +It implements schemas for: +- Tool registration and invocation +- Resource management and subscriptions +- Prompt templates and arguments +- Gateway federation +- RPC message formats +- Event messages +- Admin interface + +The schemas ensure proper validation according to the MCP specification while adding +gateway-specific extensions for federation support. +""" + +import json +import logging +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional, Union + +from pydantic import ( + AnyHttpUrl, + BaseModel, + Field, + model_validator, + root_validator, + validator, +) + +from mcpgateway.types import ImageContent +from mcpgateway.types import Prompt as MCPPrompt +from mcpgateway.types import Resource as MCPResource +from mcpgateway.types import ResourceContent, TextContent +from mcpgateway.types import Tool as MCPTool +from mcpgateway.utils.services_auth import decode_auth, encode_auth + +logger = logging.getLogger(__name__) + + +def to_camel_case(s: str) -> str: + """ + Convert a string from snake_case to camelCase. + + Args: + s (str): The string to be converted, which is assumed to be in snake_case. + + Returns: + str: The string converted to camelCase. + + Example: + >>> to_camel_case("hello_world_example") + 'helloWorldExample' + """ + return "".join(word.capitalize() if i else word for i, word in enumerate(s.split("_"))) + + +def encode_datetime(v: datetime) -> str: + """ + Convert a datetime object to an ISO 8601 formatted string. + + Args: + v (datetime): The datetime object to be encoded. + + Returns: + str: The ISO 8601 formatted string representation of the datetime object. + + Example: + >>> encode_datetime(datetime(2023, 5, 22, 14, 30, 0)) + '2023-05-22T14:30:00' + """ + return v.isoformat() + + +# --- Base Model --- +class BaseModelWithConfig(BaseModel): + """Base model with common configuration. + + Provides: + - ORM mode for SQLAlchemy integration + - JSON encoders for datetime handling + - Automatic conversion from snake_case to camelCase for output + """ + + class Config: + """ + A configuration class that provides default behaviors for how to handle serialization, + alias generation, enum values, and extra fields when working with models. + + Attributes: + from_attributes (bool): Flag to indicate if attributes should be taken from model fields. + alias_generator (callable): Function used to generate aliases for field names (e.g., converting to camelCase). + populate_by_name (bool): Flag to specify whether to populate fields by name during initialization. + json_encoders (dict): Custom JSON encoders for specific types, such as datetime encoding. + use_enum_values (bool): Flag to determine if enum values should be serialized or the enum type itself. + extra (str): Defines behavior for extra fields in models. The "ignore" option means extra fields are ignored. + json_schema_extra (dict): Additional schema information, e.g., specifying that fields can be nullable. + + """ + + from_attributes = True + alias_generator = to_camel_case + populate_by_name = True + json_encoders = {datetime: encode_datetime} + use_enum_values = True + extra = "ignore" + json_schema_extra = {"nullable": True} + + def to_dict(self, use_alias: bool = False) -> Dict[str, Any]: + """ + Converts the model instance into a dictionary representation. + + Args: + use_alias (bool): Whether to use aliases for field names (default is False). If True, + field names will be converted using the alias generator function. + + Returns: + Dict[str, Any]: A dictionary where keys are field names and values are corresponding field values, + with any nested models recursively converted to dictionaries. + """ + output = {} + for key, value in self.dict(by_alias=use_alias).items(): + output[key] = value if not isinstance(value, BaseModel) else value.to_dict(use_alias) + return output + + +# --- Metrics Schemas --- + + +class ToolMetrics(BaseModelWithConfig): + """ + Represents the performance and execution statistics for a tool. + + Attributes: + total_executions (int): Total number of tool invocations. + successful_executions (int): Number of successful tool invocations. + failed_executions (int): Number of failed tool invocations. + failure_rate (float): Failure rate (failed invocations / total invocations). + min_response_time (Optional[float]): Minimum response time in seconds. + max_response_time (Optional[float]): Maximum response time in seconds. + avg_response_time (Optional[float]): Average response time in seconds. + last_execution_time (Optional[datetime]): Timestamp of the most recent invocation. + """ + + total_executions: int = Field(..., description="Total number of tool invocations") + successful_executions: int = Field(..., description="Number of successful tool invocations") + failed_executions: int = Field(..., description="Number of failed tool invocations") + failure_rate: float = Field(..., description="Failure rate (failed invocations / total invocations)") + min_response_time: Optional[float] = Field(None, description="Minimum response time in seconds") + max_response_time: Optional[float] = Field(None, description="Maximum response time in seconds") + avg_response_time: Optional[float] = Field(None, description="Average response time in seconds") + last_execution_time: Optional[datetime] = Field(None, description="Timestamp of the most recent invocation") + + +class ResourceMetrics(BaseModelWithConfig): + """ + Represents the performance and execution statistics for a resource. + + Attributes: + total_executions (int): Total number of resource invocations. + successful_executions (int): Number of successful resource invocations. + failed_executions (int): Number of failed resource invocations. + failure_rate (float): Failure rate (failed invocations / total invocations). + min_response_time (Optional[float]): Minimum response time in seconds. + max_response_time (Optional[float]): Maximum response time in seconds. + avg_response_time (Optional[float]): Average response time in seconds. + last_execution_time (Optional[datetime]): Timestamp of the most recent invocation. + """ + + total_executions: int = Field(..., description="Total number of resource invocations") + successful_executions: int = Field(..., description="Number of successful resource invocations") + failed_executions: int = Field(..., description="Number of failed resource invocations") + failure_rate: float = Field(..., description="Failure rate (failed invocations / total invocations)") + min_response_time: Optional[float] = Field(None, description="Minimum response time in seconds") + max_response_time: Optional[float] = Field(None, description="Maximum response time in seconds") + avg_response_time: Optional[float] = Field(None, description="Average response time in seconds") + last_execution_time: Optional[datetime] = Field(None, description="Timestamp of the most recent invocation") + + +class ServerMetrics(BaseModelWithConfig): + """ + Represents the performance and execution statistics for a server. + + Attributes: + total_executions (int): Total number of server invocations. + successful_executions (int): Number of successful server invocations. + failed_executions (int): Number of failed server invocations. + failure_rate (float): Failure rate (failed invocations / total invocations). + min_response_time (Optional[float]): Minimum response time in seconds. + max_response_time (Optional[float]): Maximum response time in seconds. + avg_response_time (Optional[float]): Average response time in seconds. + last_execution_time (Optional[datetime]): Timestamp of the most recent invocation. + """ + + total_executions: int = Field(..., description="Total number of server invocations") + successful_executions: int = Field(..., description="Number of successful server invocations") + failed_executions: int = Field(..., description="Number of failed server invocations") + failure_rate: float = Field(..., description="Failure rate (failed invocations / total invocations)") + min_response_time: Optional[float] = Field(None, description="Minimum response time in seconds") + max_response_time: Optional[float] = Field(None, description="Maximum response time in seconds") + avg_response_time: Optional[float] = Field(None, description="Average response time in seconds") + last_execution_time: Optional[datetime] = Field(None, description="Timestamp of the most recent invocation") + + +class PromptMetrics(BaseModelWithConfig): + """ + Represents the performance and execution statistics for a prompt. + + Attributes: + total_executions (int): Total number of prompt invocations. + successful_executions (int): Number of successful prompt invocations. + failed_executions (int): Number of failed prompt invocations. + failure_rate (float): Failure rate (failed invocations / total invocations). + min_response_time (Optional[float]): Minimum response time in seconds. + max_response_time (Optional[float]): Maximum response time in seconds. + avg_response_time (Optional[float]): Average response time in seconds. + last_execution_time (Optional[datetime]): Timestamp of the most recent invocation. + """ + + total_executions: int = Field(..., description="Total number of prompt invocations") + successful_executions: int = Field(..., description="Number of successful prompt invocations") + failed_executions: int = Field(..., description="Number of failed prompt invocations") + failure_rate: float = Field(..., description="Failure rate (failed invocations / total invocations)") + min_response_time: Optional[float] = Field(None, description="Minimum response time in seconds") + max_response_time: Optional[float] = Field(None, description="Maximum response time in seconds") + avg_response_time: Optional[float] = Field(None, description="Average response time in seconds") + last_execution_time: Optional[datetime] = Field(None, description="Timestamp of the most recent invocation") + + +# --- JSON Path API modifier Schema + + +class JsonPathModifier(BaseModelWithConfig): + """Schema for JSONPath queries. + + Provides the structure for parsing JSONPath queries and optional mapping. + """ + + jsonpath: Optional[str] = Field(None, description="JSONPath expression for querying JSON data.") + mapping: Optional[Dict[str, str]] = Field(None, description="Mapping of fields from original data to output.") + + +# --- Tool Schemas --- +# Authentication model +class AuthenticationValues(BaseModelWithConfig): + """Schema for all Authentications. + Provides the authentication values for different types of authentication. + """ + + auth_type: Optional[str] = Field(None, description="Type of authentication: basic, bearer, headers or None") + auth_value: Optional[str] = Field(None, description="Encoded Authentication values") + + # Only For tool read and view tool + username: str = Field("", description="Username for basic authentication") + password: str = Field("", description="Password for basic authentication") + token: str = Field("", description="Bearer token for authentication") + auth_header_key: str = Field("", description="Key for custom headers authentication") + auth_header_value: str = Field("", description="Value for custom headers authentication") + + +class ToolCreate(BaseModelWithConfig): + """Schema for creating a new tool. + + Supports both MCP-compliant tools and REST integrations. Validates: + - Unique tool name + - Valid endpoint URL + - Valid JSON Schema for input validation + - Request type (HTTP method) + - Integration type: 'MCP' for MCP-compliant tools or 'REST' for REST integrations + - Optional authentication credentials: BasicAuth or BearerTokenAuth or HeadersAuth. + """ + + name: str = Field(..., description="Unique name for the tool") + url: Union[str, AnyHttpUrl] = Field(None, description="Tool endpoint URL") + description: Optional[str] = Field(None, description="Tool description") + request_type: Literal["GET", "POST", "PUT", "DELETE", "SSE", "STDIO"] = Field("SSE", description="HTTP method to be used for invoking the tool") + integration_type: Literal["MCP", "REST"] = Field("MCP", description="Tool integration type: 'MCP' for MCP-compliant tools, 'REST' for REST integrations") + headers: Optional[Dict[str, str]] = Field(None, description="Additional headers to send when invoking the tool") + input_schema: Optional[Dict[str, Any]] = Field( + default_factory=lambda: {"type": "object", "properties": {}}, + description="JSON Schema for validating tool parameters", + ) + jsonpath_filter: Optional[str] = Field(default="", description="JSON modification filter") + auth: Optional[AuthenticationValues] = Field(None, description="Authentication credentials (Basic or Bearer Token or custom headers) if required") + gateway_id: Optional[int] = Field(None, description="id of gateway for the tool") + + @root_validator(pre=True) + def assemble_auth(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """ + Assemble authentication information from separate keys if provided. + + Looks for keys "auth_type", "auth_username", "auth_password", "auth_token", "auth_header_key" and "auth_header_value". + Constructs the "auth" field as a dictionary suitable for BasicAuth or BearerTokenAuth or HeadersAuth. + + Args: + values: Dict with authentication information + + Returns: + Dict: Reformatedd values dict + """ + logger.debug( + "Assembling auth in ToolCreate with raw values", + extra={ + "auth_type": values.get("auth_type"), + "auth_username": values.get("auth_username"), + "auth_password": values.get("auth_password"), + "auth_token": values.get("auth_token"), + "auth_header_key": values.get("auth_header_key"), + "auth_header_value": values.get("auth_header_value"), + }, + ) + + auth_type = values.get("auth_type") + if auth_type: + if auth_type.lower() == "basic": + encoded_auth = encode_auth({"username": values.get("auth_username", ""), "password": values.get("auth_password", "")}) + values["auth"] = {"auth_type": "basic", "auth_value": encoded_auth} + elif auth_type.lower() == "bearer": + encoded_auth = encode_auth({"Authorization": f"Bearer {values.get('auth_token', '')}"}) + values["auth"] = {"auth_type": "bearer", "auth_value": encoded_auth} + elif auth_type.lower() == "authheaders": + encoded_auth = encode_auth({values.get("auth_header_key", ""): values.get("auth_header_value", "")}) + values["auth"] = {"auth_type": "authheaders", "auth_value": encoded_auth} + return values + + +class ToolUpdate(BaseModelWithConfig): + """Schema for updating an existing tool. + + Similar to ToolCreate but all fields are optional to allow partial updates. + """ + + name: Optional[str] = Field(None, description="Unique name for the tool") + url: Optional[Union[str, AnyHttpUrl]] = Field(None, description="Tool endpoint URL") + description: Optional[str] = Field(None, description="Tool description") + request_type: Optional[Literal["GET", "POST", "PUT", "DELETE", "SSE", "STDIO"]] = Field(None, description="HTTP method to be used for invoking the tool") + integration_type: Optional[Literal["MCP", "REST"]] = Field(None, description="Tool integration type") + headers: Optional[Dict[str, str]] = Field(None, description="Additional headers to send when invoking the tool") + input_schema: Optional[Dict[str, Any]] = Field(None, description="JSON Schema for validating tool parameters") + jsonpath_filter: Optional[str] = Field(None, description="JSON path filter for rpc tool calls") + auth: Optional[AuthenticationValues] = Field(None, description="Authentication credentials (Basic or Bearer Token or custom headers) if required") + gateway_id: Optional[int] = Field(None, description="id of gateway for the tool") + + @root_validator(pre=True) + def assemble_auth(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """ + Assemble authentication information from separate keys if provided. + + Looks for keys "auth_type", "auth_username", "auth_password", "auth_token", "auth_header_key" and "auth_header_value". + Constructs the "auth" field as a dictionary suitable for BasicAuth or BearerTokenAuth or HeadersAuth. + + Args: + values: Dict with authentication information + + Returns: + Dict: Reformatedd values dict + """ + + logger.debug( + "Assembling auth in ToolCreate with raw values", + extra={ + "auth_type": values.get("auth_type"), + "auth_username": values.get("auth_username"), + "auth_password": values.get("auth_password"), + "auth_token": values.get("auth_token"), + "auth_header_key": values.get("auth_header_key"), + "auth_header_value": values.get("auth_header_value"), + }, + ) + + auth_type = values.get("auth_type") + if auth_type: + if auth_type.lower() == "basic": + encoded_auth = encode_auth({"username": values.get("auth_username", ""), "password": values.get("auth_password", "")}) + values["auth"] = {"auth_type": "basic", "auth_value": encoded_auth} + elif auth_type.lower() == "bearer": + encoded_auth = encode_auth({"Authorization": f"Bearer {values.get('auth_token', '')}"}) + values["auth"] = {"auth_type": "bearer", "auth_value": encoded_auth} + elif auth_type.lower() == "authheaders": + encoded_auth = encode_auth({values.get("auth_header_key", ""): values.get("auth_header_value", "")}) + values["auth"] = {"auth_type": "authheaders", "auth_value": encoded_auth} + return values + + +class ToolRead(BaseModelWithConfig): + """Schema for reading tool information. + + Includes all tool fields plus: + - Database ID + - Creation/update timestamps + - Active status + - Gateway ID for federation + - Execution count indicating the number of times the tool has been executed. + - Metrics: Aggregated metrics for the tool invocations. + - Request type and authentication settings. + """ + + id: int + name: str + url: str + description: Optional[str] + request_type: str + integration_type: str + headers: Optional[Dict[str, str]] + input_schema: Dict[str, Any] + jsonpath_filter: Optional[str] + auth: Optional[AuthenticationValues] + created_at: datetime + updated_at: datetime + is_active: bool + gateway_id: Optional[int] + execution_count: int + metrics: ToolMetrics + + class Config(BaseModelWithConfig.Config): + """ + A configuration class that inherits from BaseModelWithConfig.Config. + This class may be used to define specific configurations, extending + the base functionality of BaseModelWithConfig. + """ + + +class ToolInvocation(BaseModelWithConfig): + """Schema for tool invocation requests. + + Captures: + - Tool name to invoke + - Arguments matching tool's input schema + """ + + name: str = Field(..., description="Name of tool to invoke") + arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments matching tool's input schema") + + +class ToolResult(BaseModelWithConfig): + """Schema for tool invocation results. + + Supports: + - Multiple content types (text/image) + - Error reporting + - Optional error messages + """ + + content: List[Union[TextContent, ImageContent]] + is_error: bool = False + error_message: Optional[str] = None + + +class ResourceCreate(BaseModelWithConfig): + """Schema for creating a new resource. + + Supports: + - Static resources with URI + - Template resources with parameters + - Both text and binary content + """ + + uri: str = Field(..., description="Unique URI for the resource") + name: str = Field(..., description="Human-readable resource name") + description: Optional[str] = Field(None, description="Resource description") + mime_type: Optional[str] = Field(None, description="Resource MIME type") + template: Optional[str] = Field(None, description="URI template for parameterized resources") + content: Union[str, bytes] = Field(..., description="Resource content (text or binary)") + + +class ResourceUpdate(BaseModelWithConfig): + """Schema for updating an existing resource. + + Similar to ResourceCreate but URI is not required and all fields are optional. + """ + + name: Optional[str] = Field(None, description="Human-readable resource name") + description: Optional[str] = Field(None, description="Resource description") + mime_type: Optional[str] = Field(None, description="Resource MIME type") + template: Optional[str] = Field(None, description="URI template for parameterized resources") + content: Optional[Union[str, bytes]] = Field(None, description="Resource content (text or binary)") + + +class ResourceRead(BaseModelWithConfig): + """Schema for reading resource information. + + Includes all resource fields plus: + - Database ID + - Content size + - Creation/update timestamps + - Active status + - Metrics: Aggregated metrics for the resource invocations. + """ + + id: int + uri: str + name: str + description: Optional[str] + mime_type: Optional[str] + size: Optional[int] + created_at: datetime + updated_at: datetime + is_active: bool + metrics: ResourceMetrics + + +class ResourceSubscription(BaseModelWithConfig): + """Schema for resource subscriptions. + + Tracks: + - Resource URI being subscribed to + - Unique subscriber identifier + """ + + uri: str = Field(..., description="URI of resource to subscribe to") + subscriber_id: str = Field(..., description="Unique subscriber identifier") + + +class ResourceNotification(BaseModelWithConfig): + """Schema for resource update notifications. + + Contains: + - Resource URI + - Updated content + - Update timestamp + """ + + uri: str + content: ResourceContent + timestamp: datetime = Field(default_factory=datetime.utcnow) + + +# --- Prompt Schemas --- + + +class PromptArgument(BaseModelWithConfig): + """Schema for prompt template arguments. + + Defines: + - Argument name + - Optional description + - Required flag + """ + + name: str = Field(..., description="Argument name") + description: Optional[str] = Field(None, description="Argument description") + required: bool = Field(default=False, description="Whether argument is required") + + class Config(BaseModelWithConfig.Config): + """ + A configuration class that inherits from BaseModelWithConfig.Config. + + This class defines an example schema for configuration, which includes: + - 'name': A string representing the name of the configuration (e.g., "language"). + - 'description': A brief description of the configuration (e.g., "Programming language"). + - 'required': A boolean indicating if the configuration is mandatory (e.g., True). + + The `schema_extra` attribute provides an example of how the configuration should be structured. + """ + + schema_extra = {"example": {"name": "language", "description": "Programming language", "required": True}} + + +class PromptCreate(BaseModelWithConfig): + """Schema for creating a new prompt template. + + Includes: + - Template name and description + - Template text + - Expected arguments + """ + + name: str = Field(..., description="Unique name for the prompt") + description: Optional[str] = Field(None, description="Prompt description") + template: str = Field(..., description="Prompt template text") + arguments: List[PromptArgument] = Field(default_factory=list, description="List of arguments for the template") + + +class PromptUpdate(BaseModelWithConfig): + """Schema for updating an existing prompt. + + Similar to PromptCreate but all fields are optional to allow partial updates. + """ + + name: Optional[str] = Field(None, description="Unique name for the prompt") + description: Optional[str] = Field(None, description="Prompt description") + template: Optional[str] = Field(None, description="Prompt template text") + arguments: Optional[List[PromptArgument]] = Field(None, description="List of arguments for the template") + + +class PromptRead(BaseModelWithConfig): + """Schema for reading prompt information. + + Includes all prompt fields plus: + - Database ID + - Creation/update timestamps + - Active status + - Metrics: Aggregated metrics for the prompt invocations. + """ + + id: int + name: str + description: Optional[str] + template: str + arguments: List[PromptArgument] + created_at: datetime + updated_at: datetime + is_active: bool + metrics: PromptMetrics + + +class PromptInvocation(BaseModelWithConfig): + """Schema for prompt invocation requests. + + Contains: + - Prompt name to use + - Arguments for template rendering + """ + + name: str = Field(..., description="Name of prompt to use") + arguments: Dict[str, str] = Field(default_factory=dict, description="Arguments for template rendering") + + +# --- Gateway Schemas --- + + +class GatewayCreate(BaseModelWithConfig): + """Schema for registering a new federation gateway. + + Captures: + - Gateway name + - Endpoint URL + - Optional description + - Authentication type: basic, bearer, headers + - Authentication credentials: username/password or token or custom headers + """ + + name: str = Field(..., description="Unique name for the gateway") + url: Union[str, AnyHttpUrl] = Field(..., description="Gateway endpoint URL") + description: Optional[str] = Field(None, description="Gateway description") + + # Authorizations + auth_type: Optional[str] = Field(None, description="Type of authentication: basic, bearer, headers, or none") + # Fields for various types of authentication + auth_username: Optional[str] = Field(None, description="Username for basic authentication") + auth_password: Optional[str] = Field(None, description="Password for basic authentication") + auth_token: Optional[str] = Field(None, description="Token for bearer authentication") + auth_header_key: Optional[str] = Field(None, description="Key for custom headers authentication") + auth_header_value: Optional[str] = Field(None, description="Value for custom headers authentication") + + # Adding `auth_value` as an alias for better access post-validation + auth_value: Optional[str] = None + + @validator("url", pre=True) + def ensure_url_scheme(cls, v: str) -> str: + """ + Ensure URL has an http/https scheme. + + Args: + v: Input url + + Returns: + str: URL with correct schema + + """ + if isinstance(v, str) and not (v.startswith("http://") or v.startswith("https://")): + return f"http://{v}" + return v + + @validator("auth_value", pre=True, always=True) + def create_auth_value(cls, v, values): + """ + This validator will run before the model is fully instantiated (pre=True) + It will process the auth fields based on auth_type and generate auth_value. + + Args: + v: Input url + values: Dict containing auth_type + + Returns: + str: Auth value + """ + auth_type = values.get("auth_type") + + if (auth_type is None) or (auth_type == ""): + return v # If no auth_type is provided, no need to create auth_value + + # Process the auth fields and generate auth_value based on auth_type + auth_value = cls._process_auth_fields(values) + + return auth_value + + @staticmethod + def _process_auth_fields(values: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """ + Processes the input authentication fields and returns the correct auth_value. + This method is called based on the selected auth_type. + + Args: + values: Dict containing auth fields + + Returns: + Dict with encoded auth + + Raises: + ValueError: If auth_type is invalid + """ + auth_type = values.get("auth_type") + + if auth_type == "basic": + # For basic authentication, both username and password must be present + username = values.get("auth_username") + password = values.get("auth_password") + + if not username or not password: + raise ValueError("For 'basic' auth, both 'auth_username' and 'auth_password' must be provided.") + + return encode_auth({"username": username, "password": password}) + + if auth_type == "bearer": + # For bearer authentication, only token is required + token = values.get("auth_token") + + if not token: + raise ValueError("For 'bearer' auth, 'auth_token' must be provided.") + + return encode_auth({"Authorization": f"Bearer {token}"}) + + if auth_type == "authheaders": + # For headers authentication, both key and value must be present + header_key = values.get("auth_header_key") + header_value = values.get("auth_header_value") + + if not header_key or not header_value: + raise ValueError("For 'headers' auth, both 'auth_header_key' and 'auth_header_value' must be provided.") + + return encode_auth({header_key: header_value}) + + raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, or headers.") + + +class GatewayUpdate(BaseModelWithConfig): + """Schema for updating an existing federation gateway. + + Similar to GatewayCreate but all fields are optional to allow partial updates. + """ + + name: Optional[str] = Field(None, description="Unique name for the gateway") + url: Optional[Union[str, AnyHttpUrl]] = Field(None, description="Gateway endpoint URL") + description: Optional[str] = Field(None, description="Gateway description") + + name: Optional[str] = Field(None, description="Unique name for the prompt") + # Authorizations + auth_type: Optional[str] = Field(None, description="auth_type: basic, bearer, headers or None") + auth_username: Optional[str] = Field(None, description="username for basic authentication") + auth_password: Optional[str] = Field(None, description="password for basic authentication") + auth_token: Optional[str] = Field(None, description="token for bearer authentication") + auth_header_key: Optional[str] = Field(None, description="key for custom headers authentication") + auth_header_value: Optional[str] = Field(None, description="vallue for custom headers authentication") + + # Adding `auth_value` as an alias for better access post-validation + auth_value: Optional[str] = None + + @validator("url", pre=True) + def ensure_url_scheme(cls, v: Optional[str]) -> Optional[str]: + """ + Ensure URL has an http/https scheme. + + Args: + v: Input URL + + Returns: + str: Validated URL + """ + if isinstance(v, str) and not (v.startswith("http://") or v.startswith("https://")): + return f"http://{v}" + return v + + @validator("auth_value", pre=True, always=True) + def create_auth_value(cls, v, values): + """ + This validator will run before the model is fully instantiated (pre=True) + It will process the auth fields based on auth_type and generate auth_value. + + Args: + v: Input URL + values: Dict containing auth_type + + Returns: + str: Auth value or URL + """ + auth_type = values.get("auth_type") + + if (auth_type is None) or (auth_type == ""): + return v # If no auth_type is provided, no need to create auth_value + + # Process the auth fields and generate auth_value based on auth_type + auth_value = cls._process_auth_fields(values) + + return auth_value + + @staticmethod + def _process_auth_fields(values: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """ + Processes the input authentication fields and returns the correct auth_value. + This method is called based on the selected auth_type. + + Args: + values: Dict container auth information auth_type, auth_username, auth_password, auth_token, auth_header_key and auth_header_value + + Returns: + dict: Encoded auth information + + Raises: + ValueError: If auth type is invalid + """ + auth_type = values.get("auth_type") + + if auth_type == "basic": + # For basic authentication, both username and password must be present + username = values.get("auth_username") + password = values.get("auth_password") + + if not username or not password: + raise ValueError("For 'basic' auth, both 'auth_username' and 'auth_password' must be provided.") + + return encode_auth({"username": username, "password": password}) + + if auth_type == "bearer": + # For bearer authentication, only token is required + token = values.get("auth_token") + + if not token: + raise ValueError("For 'bearer' auth, 'auth_token' must be provided.") + + return encode_auth({"Authorization": f"Bearer {token}"}) + + if auth_type == "authheaders": + # For headers authentication, both key and value must be present + header_key = values.get("auth_header_key") + header_value = values.get("auth_header_value") + + if not header_key or not header_value: + raise ValueError("For 'headers' auth, both 'auth_header_key' and 'auth_header_value' must be provided.") + + return encode_auth({header_key: header_value}) + + raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, or headers.") + + +class GatewayRead(BaseModelWithConfig): + """Schema for reading gateway information. + + Includes all gateway fields plus: + - Database ID + - Capabilities dictionary + - Creation/update timestamps + - Active status + - Last seen timestamp + - Authentication type: basic, bearer, headers + - Authentication value: username/password or token or custom headers + + Auto Populated fields: + - Authentication username: for basic auth + - Authentication password: for basic auth + - Authentication token: for bearer auth + - Authentication header key: for headers auth + - Authentication header value: for headers auth + """ + + id: int = Field(None, description="Unique ID of the gateway") + name: str = Field(..., description="Unique name for the gateway") + url: str = Field(..., description="Gateway endpoint URL") + description: Optional[str] = Field(None, description="Gateway description") + capabilities: Dict[str, Any] = Field(default_factory=dict, description="Gateway capabilities") + created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation timestamp") + updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update timestamp") + is_active: bool = Field(default=True, description="Is the gateway active?") + last_seen: Optional[datetime] = Field(default_factory=datetime.utcnow, description="Last seen timestamp") + + # Authorizations + auth_type: Optional[str] = Field(None, description="auth_type: basic, bearer, headers or None") + auth_value: Optional[str] = Field(None, description="auth value: username/password or token or custom headers") + + # auth_value will populate the following fields + auth_username: Optional[str] = Field(None, description="username for basic authentication") + auth_password: Optional[str] = Field(None, description="password for basic authentication") + auth_token: Optional[str] = Field(None, description="token for bearer authentication") + auth_header_key: Optional[str] = Field(None, description="key for custom headers authentication") + auth_header_value: Optional[str] = Field(None, description="vallue for custom headers authentication") + + # This will be the main method to automatically populate fields + @model_validator(mode="after") + def _populate_auth(cls, values: Dict[str, Any]) -> Dict[str, Any]: + auth_type = values.auth_type + auth_value_encoded = values.auth_value + auth_value = decode_auth(auth_value_encoded) + if auth_type == "basic": + u = auth_value.get("username") + p = auth_value.get("password") + if not u or not p: + raise ValueError("basic auth requires both username and password") + values.auth_username, values.auth_password = u, p + + elif auth_type == "bearer": + auth = auth_value.get("Authorization") + if not (isinstance(auth, str) and auth.startswith("Bearer ")): + raise ValueError("bearer auth requires an Authorization header of the form 'Bearer '") + values.auth_token = auth.removeprefix("Bearer ") + + elif auth_type == "authheaders": + # must be exactly one header + if len(auth_value) != 1: + raise ValueError("authheaders requires exactly one key/value pair") + k, v = next(iter(auth_value.items())) + values.auth_header_key, values.auth_header_value = k, v + + return values + + +class FederatedTool(BaseModelWithConfig): + """Schema for tools provided by federated gateways. + + Contains: + - Tool definition + - Source gateway information + """ + + tool: MCPTool + gateway_id: str + gateway_name: str + gateway_url: str + + +class FederatedResource(BaseModelWithConfig): + """Schema for resources from federated gateways. + + Contains: + - Resource definition + - Source gateway information + """ + + resource: MCPResource + gateway_id: str + gateway_name: str + gateway_url: str + + +class FederatedPrompt(BaseModelWithConfig): + """Schema for prompts from federated gateways. + + Contains: + - Prompt definition + - Source gateway information + """ + + prompt: MCPPrompt + gateway_id: str + gateway_name: str + gateway_url: str + + +# --- RPC Schemas --- + + +class RPCRequest(BaseModelWithConfig): + """Schema for JSON-RPC 2.0 requests. + + Validates: + - Protocol version + - Method name + - Optional parameters + - Optional request ID + """ + + jsonrpc: Literal["2.0"] + method: str + params: Optional[Dict[str, Any]] = None + id: Optional[Union[int, str]] = None + + +class RPCResponse(BaseModelWithConfig): + """Schema for JSON-RPC 2.0 responses. + + Contains: + - Protocol version + - Result or error + - Request ID + """ + + jsonrpc: Literal["2.0"] + result: Optional[Any] = None + error: Optional[Dict[str, Any]] = None + id: Optional[Union[int, str]] = None + + +# --- Event and Admin Schemas --- + + +class EventMessage(BaseModelWithConfig): + """Schema for SSE event messages. + + Includes: + - Event type + - Event data payload + - Event timestamp + """ + + type: str = Field(..., description="Event type (tool_added, resource_updated, etc)") + data: Dict[str, Any] = Field(..., description="Event payload") + timestamp: datetime = Field(default_factory=datetime.utcnow) + + +class AdminToolCreate(BaseModelWithConfig): + """Schema for creating tools via admin UI. + + Handles: + - Basic tool information + - JSON string inputs for headers/schema + """ + + name: str + url: str + description: Optional[str] = None + integration_type: str = "MCP" + headers: Optional[str] = None # JSON string + input_schema: Optional[str] = None # JSON string + + @validator("headers", "input_schema") + def validate_json(cls, v: Optional[str]) -> Optional[Dict[str, Any]]: + """ + Validate and parse JSON string inputs. + + Args: + v: Input string + + Returns: + dict: Output JSON version of v + + Raises: + ValueError: When unable to convert to JSON + """ + if not v: + return None + try: + return json.loads(v) + except json.JSONDecodeError: + raise ValueError("Invalid JSON") + + +class AdminGatewayCreate(BaseModelWithConfig): + """Schema for creating gateways via admin UI. + + Captures: + - Gateway name + - Endpoint URL + - Optional description + """ + + name: str + url: str + description: Optional[str] = None + + +# --- New Schemas for Status Toggle Operations --- + + +class StatusToggleRequest(BaseModelWithConfig): + """Request schema for toggling active status.""" + + activate: bool = Field(..., description="Whether to activate (true) or deactivate (false) the item") + + +class StatusToggleResponse(BaseModelWithConfig): + """Response schema for status toggle operations.""" + + id: int + name: str + is_active: bool + message: str = Field(..., description="Success message") + + +# --- Optional Filter Parameters for Listing Operations --- + + +class ListFilters(BaseModelWithConfig): + """Filtering options for list operations.""" + + include_inactive: bool = Field(False, description="Whether to include inactive items in the results") + + +# --- Server Schemas --- + + +class ServerCreate(BaseModelWithConfig): + """Schema for creating a new server. + + Attributes: + name: The server's name (required). + description: Optional text description. + icon: Optional URL for the server’s icon. + associated_tools: Optional list of tool IDs (as strings). + associated_resources: Optional list of resource IDs (as strings). + associated_prompts: Optional list of prompt IDs (as strings). + """ + + name: str = Field(..., description="The server's name") + description: Optional[str] = Field(None, description="Server description") + icon: Optional[str] = Field(None, description="URL for the server's icon") + associated_tools: Optional[List[str]] = Field(None, description="Comma-separated tool IDs") + associated_resources: Optional[List[str]] = Field(None, description="Comma-separated resource IDs") + associated_prompts: Optional[List[str]] = Field(None, description="Comma-separated prompt IDs") + + @validator("associated_tools", "associated_resources", "associated_prompts", pre=True) + def split_comma_separated(cls, v): + """ + Splits a comma-separated string into a list of strings if needed. + + Args: + v: Input string + + Returns: + list: Comma separated array of input string + """ + if isinstance(v, str): + return [item.strip() for item in v.split(",") if item.strip()] + return v + + +class ServerUpdate(BaseModelWithConfig): + """Schema for updating an existing server. + + All fields are optional to allow partial updates. + """ + + name: Optional[str] = Field(None, description="The server's name") + description: Optional[str] = Field(None, description="Server description") + icon: Optional[str] = Field(None, description="URL for the server's icon") + associated_tools: Optional[List[str]] = Field(None, description="Comma-separated tool IDs") + associated_resources: Optional[List[str]] = Field(None, description="Comma-separated resource IDs") + associated_prompts: Optional[List[str]] = Field(None, description="Comma-separated prompt IDs") + + @validator("associated_tools", "associated_resources", "associated_prompts", pre=True) + def split_comma_separated(cls, v): + """ + Splits a comma-separated string into a list of strings if needed. + + Args: + v: Input string + + Returns: + list: Comma separated array of input string + """ + if isinstance(v, str): + return [item.strip() for item in v.split(",") if item.strip()] + return v + + +class ServerRead(BaseModelWithConfig): + """Schema for reading server information. + + Includes all server fields plus: + - Database ID + - Associated tool, resource, and prompt IDs + - Creation/update timestamps + - Active status + - Metrics: Aggregated metrics for the server invocations. + """ + + id: int + name: str + description: Optional[str] + icon: Optional[str] + created_at: datetime + updated_at: datetime + is_active: bool + associated_tools: List[int] = [] + associated_resources: List[int] = [] + associated_prompts: List[int] = [] + metrics: ServerMetrics + + @root_validator(pre=True) + def populate_associated_ids(cls, values): + """ + Pre-validation method that converts associated objects to their 'id'. + + This method checks 'associated_tools', 'associated_resources', and + 'associated_prompts' in the input and replaces each object with its `id` + if present. + + Args: + values (dict): The input values. + + Returns: + dict: Updated values with object ids, or the original values if no + changes are made. + """ + # If values is not a dict (e.g. it's a Server instance), convert it + if not isinstance(values, dict): + try: + values = vars(values) + except Exception: + return values + if "associated_tools" in values and values["associated_tools"]: + values["associated_tools"] = [tool.id if hasattr(tool, "id") else tool for tool in values["associated_tools"]] + if "associated_resources" in values and values["associated_resources"]: + values["associated_resources"] = [res.id if hasattr(res, "id") else res for res in values["associated_resources"]] + if "associated_prompts" in values and values["associated_prompts"]: + values["associated_prompts"] = [prompt.id if hasattr(prompt, "id") else prompt for prompt in values["associated_prompts"]] + return values diff --git a/mcpgateway/services/__init__.py b/mcpgateway/services/__init__.py new file mode 100644 index 0000000000..88c4fd675c --- /dev/null +++ b/mcpgateway/services/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +"""Services Package. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Exposes core MCP Gateway services: +- Tool management +- Resource handling +- Prompt templates +- Gateway coordination +""" + +from mcpgateway.services.gateway_service import GatewayError, GatewayService +from mcpgateway.services.prompt_service import PromptError, PromptService +from mcpgateway.services.resource_service import ResourceError, ResourceService +from mcpgateway.services.tool_service import ToolError, ToolService + +__all__ = [ + "ToolService", + "ToolError", + "ResourceService", + "ResourceError", + "PromptService", + "PromptError", + "GatewayService", + "GatewayError", +] diff --git a/mcpgateway/services/completion_service.py b/mcpgateway/services/completion_service.py new file mode 100644 index 0000000000..23f6e39765 --- /dev/null +++ b/mcpgateway/services/completion_service.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +"""Completion Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements argument completion according to the MCP specification. +It handles completion suggestions for prompt arguments and resource URIs. +""" + +import logging +from typing import Any, Dict, List + +from sqlalchemy import select +from sqlalchemy.orm import Session + +from mcpgateway.db import Prompt as DbPrompt +from mcpgateway.db import Resource as DbResource +from mcpgateway.types import CompleteResult + +logger = logging.getLogger(__name__) + + +class CompletionError(Exception): + """Base class for completion errors.""" + + +class CompletionService: + """MCP completion service. + + Handles argument completion for: + - Prompt arguments based on schema + - Resource URIs with templates + - Custom completion sources + """ + + def __init__(self): + """Initialize completion service.""" + self._custom_completions: Dict[str, List[str]] = {} + + async def initialize(self) -> None: + """Initialize completion service.""" + logger.info("Initializing completion service") + + async def shutdown(self) -> None: + """Shutdown completion service.""" + logger.info("Shutting down completion service") + self._custom_completions.clear() + + async def handle_completion(self, db: Session, request: Dict[str, Any]) -> CompleteResult: + """Handle completion request. + + Args: + db: Database session + request: Completion request + + Returns: + Completion result with suggestions + + Raises: + CompletionError: If completion fails + """ + try: + # Get reference and argument info + ref = request.get("ref", {}) + ref_type = ref.get("type") + arg = request.get("argument", {}) + arg_name = arg.get("name") + arg_value = arg.get("value", "") + + if not ref_type or not arg_name: + raise CompletionError("Missing reference type or argument name") + + # Handle different reference types + if ref_type == "ref/prompt": + result = await self._complete_prompt_argument(db, ref, arg_name, arg_value) + elif ref_type == "ref/resource": + result = await self._complete_resource_uri(db, ref, arg_value) + else: + raise CompletionError(f"Invalid reference type: {ref_type}") + + return result + + except Exception as e: + logger.error(f"Completion error: {e}") + raise CompletionError(str(e)) + + async def _complete_prompt_argument(self, db: Session, ref: Dict[str, Any], arg_name: str, arg_value: str) -> CompleteResult: + """Complete prompt argument value. + + Args: + db: Database session + ref: Prompt reference + arg_name: Argument name + arg_value: Current argument value + + Returns: + Completion suggestions + + Raises: + CompletionError: If URI template is missing + """ + # Get prompt + prompt_name = ref.get("name") + if not prompt_name: + raise CompletionError("Missing prompt name") + + prompt = db.execute(select(DbPrompt).where(DbPrompt.name == prompt_name).where(DbPrompt.is_active)).scalar_one_or_none() + + if not prompt: + raise CompletionError(f"Prompt not found: {prompt_name}") + + # Find argument in schema + arg_schema = None + for arg in prompt.argument_schema.get("properties", {}).values(): + if arg.get("name") == arg_name: + arg_schema = arg + break + + if not arg_schema: + raise CompletionError(f"Argument not found: {arg_name}") + + # Get enum values if defined + if "enum" in arg_schema: + values = [v for v in arg_schema["enum"] if arg_value.lower() in str(v).lower()] + return CompleteResult( + completion={ + "values": values[:100], + "total": len(values), + "hasMore": len(values) > 100, + } + ) + + # Check custom completions + if arg_name in self._custom_completions: + values = [v for v in self._custom_completions[arg_name] if arg_value.lower() in v.lower()] + return CompleteResult( + completion={ + "values": values[:100], + "total": len(values), + "hasMore": len(values) > 100, + } + ) + + # No completions available + return CompleteResult(completion={"values": [], "total": 0, "hasMore": False}) + + async def _complete_resource_uri(self, db: Session, ref: Dict[str, Any], arg_value: str) -> CompleteResult: + """Complete resource URI. + + Args: + db: Database session + ref: Resource reference + arg_value: Current URI value + + Returns: + URI completion suggestions + + Raises: + CompletionError: If URI template is missing + """ + # Get base URI template + uri_template = ref.get("uri") + if not uri_template: + raise CompletionError("Missing URI template") + + # List matching resources + resources = db.execute(select(DbResource).where(DbResource.is_active)).scalars().all() + + # Filter by URI pattern + matches = [] + for resource in resources: + if arg_value.lower() in resource.uri.lower(): + matches.append(resource.uri) + + return CompleteResult( + completion={ + "values": matches[:100], + "total": len(matches), + "hasMore": len(matches) > 100, + } + ) + + def register_completions(self, arg_name: str, values: List[str]) -> None: + """Register custom completion values. + + Args: + arg_name: Argument name + values: Completion values + """ + self._custom_completions[arg_name] = list(values) + + def unregister_completions(self, arg_name: str) -> None: + """Unregister custom completion values. + + Args: + arg_name: Argument name + """ + self._custom_completions.pop(arg_name, None) diff --git a/mcpgateway/services/gateway_service.py b/mcpgateway/services/gateway_service.py new file mode 100644 index 0000000000..540ba364e5 --- /dev/null +++ b/mcpgateway/services/gateway_service.py @@ -0,0 +1,709 @@ +# -*- coding: utf-8 -*- +"""Gateway Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements gateway federation according to the MCP specification. +It handles: +- Gateway discovery and registration +- Request forwarding +- Capability aggregation +- Health monitoring +- Active/inactive gateway management +""" + +import asyncio +import logging +from datetime import datetime +from typing import Any, AsyncGenerator, Dict, List, Optional, Set + +import httpx +from mcp import ClientSession +from mcp.client.sse import sse_client +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session + +from mcpgateway.config import settings +from mcpgateway.db import Gateway as DbGateway +from mcpgateway.db import Tool as DbTool +from mcpgateway.schemas import GatewayCreate, GatewayRead, GatewayUpdate, ToolCreate +from mcpgateway.services.tool_service import ToolService +from mcpgateway.utils.services_auth import decode_auth + +logger = logging.getLogger(__name__) + + +class GatewayError(Exception): + """Base class for gateway-related errors.""" + + +class GatewayNotFoundError(GatewayError): + """Raised when a requested gateway is not found.""" + + +class GatewayNameConflictError(GatewayError): + """Raised when a gateway name conflicts with existing (active or inactive) gateway.""" + + def __init__(self, name: str, is_active: bool = True, gateway_id: Optional[int] = None): + """Initialize the error with gateway information. + + Args: + name: The conflicting gateway name + is_active: Whether the existing gateway is active + gateway_id: ID of the existing gateway if available + """ + self.name = name + self.is_active = is_active + self.gateway_id = gateway_id + message = f"Gateway already exists with name: {name}" + if not is_active: + message += f" (currently inactive, ID: {gateway_id})" + super().__init__(message) + + +class GatewayConnectionError(GatewayError): + """Raised when gateway connection fails.""" + + +class GatewayService: + """Service for managing federated gateways. + + Handles: + - Gateway registration and health checks + - Request forwarding + - Capability negotiation + - Federation events + - Active/inactive status management + """ + + def __init__(self): + """Initialize the gateway service.""" + self._event_subscribers: List[asyncio.Queue] = [] + self._http_client = httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) + self._health_check_interval = 60 # seconds + self._health_check_task: Optional[asyncio.Task] = None + self._active_gateways: Set[str] = set() # Track active gateway URLs + self._stream_response = None + self._pending_responses = {} + self.tool_service = ToolService() + + async def initialize(self) -> None: + """Initialize the service.""" + logger.info("Initializing gateway service") + self._health_check_task = asyncio.create_task(self._run_health_checks()) + + async def shutdown(self) -> None: + """Shutdown the service.""" + if self._health_check_task: + self._health_check_task.cancel() + try: + await self._health_check_task + except asyncio.CancelledError: + pass + + await self._http_client.aclose() + self._event_subscribers.clear() + self._active_gateways.clear() + logger.info("Gateway service shutdown complete") + + async def register_gateway(self, db: Session, gateway: GatewayCreate) -> GatewayRead: + """Register a new gateway. + + Args: + db: Database session + gateway: Gateway creation schema + + Returns: + Created gateway information + + Raises: + GatewayNameConflictError: If gateway name already exists + GatewayError: If registration fails + """ + try: + # Check for name conflicts (both active and inactive) + existing_gateway = db.execute(select(DbGateway).where(DbGateway.name == gateway.name)).scalar_one_or_none() + + if existing_gateway: + raise GatewayNameConflictError( + gateway.name, + is_active=existing_gateway.is_active, + gateway_id=existing_gateway.id, + ) + + auth_type = getattr(gateway, "auth_type", None) + auth_value = getattr(gateway, "auth_value", {}) + + # Initialize connection and get capabilities + capabilities, tools = await self._initialize_gateway(str(gateway.url), auth_value) + + # Create DB model + db_gateway = DbGateway( + name=gateway.name, url=str(gateway.url), description=gateway.description, capabilities=capabilities, last_seen=datetime.utcnow(), auth_type=auth_type, auth_value=auth_value + ) + + # Add to DB + db.add(db_gateway) + db.commit() + db.refresh(db_gateway) + + # Update tracking + self._active_gateways.add(db_gateway.url) + + # Notify subscribers + await self._notify_gateway_added(db_gateway) + + inserted_gateway = db.execute(select(DbGateway).where(DbGateway.name == gateway.name)).scalar_one_or_none() + inserted_gateway_id = inserted_gateway.id + + logger.info(f"Registered gateway: {gateway.name}") + + for tool in tools: + tool.gateway_id = inserted_gateway_id + await self.tool_service.register_tool(db=db, tool=tool) + + return GatewayRead.model_validate(gateway) + + except IntegrityError: + db.rollback() + raise GatewayError(f"Gateway already exists: {gateway.name}") + except Exception as e: + db.rollback() + raise GatewayError(f"Failed to register gateway: {str(e)}") + + async def list_gateways(self, db: Session, include_inactive: bool = False) -> List[GatewayRead]: + """List all registered gateways. + + Args: + db: Database session + include_inactive: Whether to include inactive gateways + + Returns: + List of registered gateways + """ + query = select(DbGateway) + + if not include_inactive: + query = query.where(DbGateway.is_active) + + gateways = db.execute(query).scalars().all() + return [GatewayRead.model_validate(g) for g in gateways] + + async def update_gateway(self, db: Session, gateway_id: int, gateway_update: GatewayUpdate) -> GatewayRead: + """Update a gateway. + + Args: + db: Database session + gateway_id: Gateway ID to update + gateway_update: Updated gateway data + + Returns: + Updated gateway information + + Raises: + GatewayNotFoundError: If gateway not found + GatewayError: For other update errors + GatewayNameConflictError: If gateway name conflict occurs + """ + try: + # Find gateway + gateway = db.get(DbGateway, gateway_id) + if not gateway: + raise GatewayNotFoundError(f"Gateway not found: {gateway_id}") + + if not gateway.is_active: + raise GatewayNotFoundError(f"Gateway '{gateway.name}' exists but is inactive") + + # Check for name conflicts if name is being changed + if gateway_update.name is not None and gateway_update.name != gateway.name: + existing_gateway = db.execute(select(DbGateway).where(DbGateway.name == gateway_update.name).where(DbGateway.id != gateway_id)).scalar_one_or_none() + + if existing_gateway: + raise GatewayNameConflictError( + gateway_update.name, + is_active=existing_gateway.is_active, + gateway_id=existing_gateway.id, + ) + + # Update fields if provided + if gateway_update.name is not None: + gateway.name = gateway_update.name + if gateway_update.url is not None: + gateway.url = str(gateway_update.url) + if gateway_update.description is not None: + gateway.description = gateway_update.description + + if getattr(gateway, "auth_type", None) is not None: + gateway.auth_type = gateway_update.auth_type + + # if auth_type is not None and only then check auth_value + if getattr(gateway, "auth_value", {}) != {}: + gateway.auth_value = gateway_update.auth_value + + # Try to reinitialize connection if URL changed + if gateway_update.url is not None: + try: + capabilities, _ = await self._initialize_gateway(gateway.url, gateway.auth_value) + gateway.capabilities = capabilities + gateway.last_seen = datetime.utcnow() + + # Update tracking with new URL + self._active_gateways.discard(gateway.url) + self._active_gateways.add(gateway.url) + except Exception as e: + logger.warning(f"Failed to initialize updated gateway: {e}") + + gateway.updated_at = datetime.utcnow() + db.commit() + db.refresh(gateway) + + # Notify subscribers + await self._notify_gateway_updated(gateway) + + logger.info(f"Updated gateway: {gateway.name}") + return GatewayRead.model_validate(gateway) + + except Exception as e: + db.rollback() + raise GatewayError(f"Failed to update gateway: {str(e)}") + + async def get_gateway(self, db: Session, gateway_id: int, include_inactive: bool = False) -> GatewayRead: + """Get a specific gateway by ID. + + Args: + db: Database session + gateway_id: Gateway ID + include_inactive: Whether to include inactive gateways + + Returns: + Gateway information + + Raises: + GatewayNotFoundError: If gateway not found + """ + gateway = db.get(DbGateway, gateway_id) + if not gateway: + raise GatewayNotFoundError(f"Gateway not found: {gateway_id}") + + if not gateway.is_active and not include_inactive: + raise GatewayNotFoundError(f"Gateway '{gateway.name}' exists but is inactive") + + return GatewayRead.model_validate(gateway) + + async def toggle_gateway_status(self, db: Session, gateway_id: int, activate: bool) -> GatewayRead: + """Toggle gateway active status. + + Args: + db: Database session + gateway_id: Gateway ID to toggle + activate: True to activate, False to deactivate + + Returns: + Updated gateway information + + Raises: + GatewayNotFoundError: If gateway not found + GatewayError: For other errors + """ + try: + gateway = db.get(DbGateway, gateway_id) + if not gateway: + raise GatewayNotFoundError(f"Gateway not found: {gateway_id}") + + # Update status if it's different + if gateway.is_active != activate: + gateway.is_active = activate + gateway.updated_at = datetime.utcnow() + + # Update tracking + if activate: + self._active_gateways.add(gateway.url) + # Try to initialize if activating + try: + capabilities, tools = await self._initialize_gateway(gateway.url, gateway.auth_value) + gateway.capabilities = capabilities.dict() + gateway.last_seen = datetime.utcnow() + except Exception as e: + logger.warning(f"Failed to initialize reactivated gateway: {e}") + else: + self._active_gateways.discard(gateway.url) + + db.commit() + db.refresh(gateway) + + tools = db.query(DbTool).filter(DbTool.gateway_id == gateway_id).all() + for tool in tools: + await self.tool_service.toggle_tool_status(db, tool.id, activate) + + # Notify subscribers + if activate: + await self._notify_gateway_activated(gateway) + else: + await self._notify_gateway_deactivated(gateway) + + logger.info(f"Gateway {gateway.name} {'activated' if activate else 'deactivated'}") + + return GatewayRead.model_validate(gateway) + + except Exception as e: + db.rollback() + raise GatewayError(f"Failed to toggle gateway status: {str(e)}") + + async def _notify_gateway_updated(self, gateway: DbGateway) -> None: + """ + Notify subscribers of gateway update. + + Args: + gateway: Gateway to update + """ + event = { + "type": "gateway_updated", + "data": { + "id": gateway.id, + "name": gateway.name, + "url": gateway.url, + "description": gateway.description, + "is_active": gateway.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def delete_gateway(self, db: Session, gateway_id: int) -> None: + """Permanently delete a gateway. + + Args: + db: Database session + gateway_id: Gateway ID to delete + + Raises: + GatewayNotFoundError: If gateway not found + GatewayError: For other deletion errors + """ + try: + # Find gateway + gateway = db.get(DbGateway, gateway_id) + if not gateway: + raise GatewayNotFoundError(f"Gateway not found: {gateway_id}") + + # Store gateway info for notification before deletion + gateway_info = {"id": gateway.id, "name": gateway.name, "url": gateway.url} + + # Remove associated tools + try: + db.query(DbTool).filter(DbTool.gateway_id == gateway_id).delete() + db.commit() + logger.info(f"Deleted tools associated with gateway: {gateway.name}") + except Exception as ex: + logger.warning(f"No tools found: {ex}") + + # Hard delete gateway + db.delete(gateway) + db.commit() + + # Update tracking + self._active_gateways.discard(gateway.url) + + # Notify subscribers + await self._notify_gateway_deleted(gateway_info) + + logger.info(f"Permanently deleted gateway: {gateway.name}") + + except Exception as e: + db.rollback() + raise GatewayError(f"Failed to delete gateway: {str(e)}") + + async def forward_request(self, gateway: DbGateway, method: str, params: Optional[Dict[str, Any]] = None) -> Any: + """Forward a request to a gateway. + + Args: + gateway: Gateway to forward to + method: RPC method name + params: Optional method parameters + + Returns: + Gateway response + + Raises: + GatewayConnectionError: If forwarding fails + GatewayError: If gateway gave an error + """ + if not gateway.is_active: + raise GatewayConnectionError(f"Cannot forward request to inactive gateway: {gateway.name}") + + try: + # Build RPC request + request = {"jsonrpc": "2.0", "id": 1, "method": method} + if params: + request["params"] = params + + # Directly use the persistent HTTP client (no async with) + response = await self._http_client.post(f"{gateway.url}/rpc", json=request, headers=self._get_auth_headers()) + response.raise_for_status() + result = response.json() + + # Update last seen timestamp + gateway.last_seen = datetime.utcnow() + + if "error" in result: + raise GatewayError(f"Gateway error: {result['error'].get('message')}") + return result.get("result") + + except Exception as e: + raise GatewayConnectionError(f"Failed to forward request to {gateway.name}: {str(e)}") + + async def check_gateway_health(self, gateway: DbGateway) -> bool: + """Check if a gateway is healthy. + + Args: + gateway: Gateway to check + + Returns: + True if gateway is healthy + """ + if not gateway.is_active: + return False + + try: + # Try to initialize connection + await self._initialize_gateway(gateway.url, gateway.auth_value) + + # Update last seen + gateway.last_seen = datetime.utcnow() + return True + + except Exception: + return False + + async def aggregate_capabilities(self, db: Session) -> Dict[str, Any]: + """Aggregate capabilities from all gateways. + + Args: + db: Database session + + Returns: + Combined capabilities + """ + capabilities = { + "prompts": {"listChanged": True}, + "resources": {"subscribe": True, "listChanged": True}, + "tools": {"listChanged": True}, + "logging": {}, + } + + # Get all active gateways + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + # Combine capabilities + for gateway in gateways: + if gateway.capabilities: + for key, value in gateway.capabilities.items(): + if key not in capabilities: + capabilities[key] = value + elif isinstance(value, dict): + capabilities[key].update(value) + + return capabilities + + async def subscribe_events(self) -> AsyncGenerator[Dict[str, Any], None]: + """Subscribe to gateway events. + + Yields: + Gateway event messages + """ + queue: asyncio.Queue = asyncio.Queue() + self._event_subscribers.append(queue) + try: + while True: + event = await queue.get() + yield event + finally: + self._event_subscribers.remove(queue) + + async def _initialize_gateway(self, url: str, authentication: Optional[Dict[str, str]] = {}) -> Any: + """Initialize connection to a gateway and retrieve its capabilities. + + Args: + url: Gateway URL + authentication: Optional authentication headers + + Returns: + Capabilities dictionary as provided by the gateway. + + Raises: + GatewayConnectionError: If initialization fails. + """ + try: + + async def connect_to_sse_server(server_url: str, authentication: Optional[Dict[str, str]] = {}): + """ + Connect to an MCP server running with SSE transport + + Args: + server_url: URL to connect to the server + authentication: Authentication headers for connection to URL + + Returns: + list, list: List of capabilities and tools + """ + # Store the context managers so they stay alive + decoded_auth = decode_auth(authentication) + _streams_context = sse_client(url=server_url, headers=decoded_auth) + streams = await _streams_context.__aenter__() # line 551 + + _session_context = ClientSession(*streams) + session: ClientSession = await _session_context.__aenter__() # line 554 + + # Initialize + response = await session.initialize() + capabilities = response.capabilities.model_dump(by_alias=True, exclude_none=True) + + response = await session.list_tools() + tools = response.tools + tools = [tool.model_dump(by_alias=True, exclude_none=True) for tool in tools] + tools = [ToolCreate.model_validate(tool) for tool in tools] + + await _session_context.__aexit__(None, None, None) + await _streams_context.__aexit__(None, None, None) # line 566 + + return capabilities, tools + + capabilities, tools = await connect_to_sse_server(url, authentication) + + return capabilities, tools + except Exception as e: + raise GatewayConnectionError(f"Failed to initialize gateway at {url}: {str(e)}") + + async def _run_health_checks(self) -> None: + """Run periodic health checks on all gateways.""" + while True: + try: + async with Session() as db: + # Get active gateways + gateways = db.execute(select(DbGateway).where(DbGateway.is_active)).scalars().all() + + # Check each gateway + for gateway in gateways: + try: + is_healthy = await self.check_gateway_health(gateway) + if not is_healthy: + logger.warning(f"Gateway {gateway.name} is unhealthy") + except Exception as e: + logger.error(f"Health check failed for {gateway.name}: {str(e)}") + + db.commit() + + except Exception as e: + logger.error(f"Health check run failed: {str(e)}") + + # Wait for next check + await asyncio.sleep(self._health_check_interval) + + def _get_auth_headers(self) -> Dict[str, str]: + """ + Get headers for gateway authentication. + + Returns: + dict: Authorization header dict + """ + api_key = f"{settings.basic_auth_user}:{settings.basic_auth_password}" + return {"Authorization": f"Basic {api_key}", "X-API-Key": api_key, "Content-Type": "application/json"} + + async def _notify_gateway_added(self, gateway: DbGateway) -> None: + """ + Notify subscribers of gateway addition. + + Args: + gateway: Gateway to add + """ + event = { + "type": "gateway_added", + "data": { + "id": gateway.id, + "name": gateway.name, + "url": gateway.url, + "description": gateway.description, + "is_active": gateway.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_gateway_activated(self, gateway: DbGateway) -> None: + """ + Notify subscribers of gateway activation. + + Args: + gateway: Gateway to activate + """ + event = { + "type": "gateway_activated", + "data": { + "id": gateway.id, + "name": gateway.name, + "url": gateway.url, + "is_active": True, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_gateway_deactivated(self, gateway: DbGateway) -> None: + """ + Notify subscribers of gateway deactivation. + + Args: + gateway: Gateway database object + """ + event = { + "type": "gateway_deactivated", + "data": { + "id": gateway.id, + "name": gateway.name, + "url": gateway.url, + "is_active": False, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_gateway_deleted(self, gateway_info: Dict[str, Any]) -> None: + """ + Notify subscribers of gateway deletion. + + Args: + gateway_info: Dict containing information about gateway to delete + """ + event = { + "type": "gateway_deleted", + "data": gateway_info, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_gateway_removed(self, gateway: DbGateway) -> None: + """ + Notify subscribers of gateway removal (deactivation). + + Args: + gateway: Gateway to remove + """ + event = { + "type": "gateway_removed", + "data": {"id": gateway.id, "name": gateway.name, "is_active": False}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _publish_event(self, event: Dict[str, Any]) -> None: + """ + Publish event to all subscribers. + + Args: + event: event dictionary + """ + for queue in self._event_subscribers: + await queue.put(event) diff --git a/mcpgateway/services/logging_service.py b/mcpgateway/services/logging_service.py new file mode 100644 index 0000000000..99e6ab40b8 --- /dev/null +++ b/mcpgateway/services/logging_service.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +"""Logging Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements structured logging according to the MCP specification. +It supports RFC 5424 severity levels, log level management, and log event subscriptions. +""" + +import asyncio +import logging +from datetime import datetime +from typing import Any, AsyncGenerator, Dict, List, Optional + +from mcpgateway.types import LogLevel + + +class LoggingService: + """MCP logging service. + + Implements structured logging with: + - RFC 5424 severity levels + - Log level management + - Log event subscriptions + - Logger name tracking + """ + + def __init__(self): + """Initialize logging service.""" + self._level = LogLevel.INFO + self._subscribers: List[asyncio.Queue] = [] + self._loggers: Dict[str, logging.Logger] = {} + + async def initialize(self) -> None: + """Initialize logging service.""" + # Configure root logger + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + ) + self._loggers[""] = logging.getLogger() + logging.info("Logging service initialized") + + async def shutdown(self) -> None: + """Shutdown logging service.""" + # Clear subscribers + self._subscribers.clear() + logging.info("Logging service shutdown") + + def get_logger(self, name: str) -> logging.Logger: + """Get or create logger instance. + + Args: + name: Logger name + + Returns: + Logger instance + """ + if name not in self._loggers: + logger = logging.getLogger(name) + + # Set level to match service level + log_level = getattr(logging, self._level.upper()) + logger.setLevel(log_level) + + self._loggers[name] = logger + + return self._loggers[name] + + async def set_level(self, level: LogLevel) -> None: + """Set minimum log level. + + This updates the level for all registered loggers. + + Args: + level: New log level + """ + self._level = level + + # Update all loggers + log_level = getattr(logging, level.upper()) + for logger in self._loggers.values(): + logger.setLevel(log_level) + + await self.notify(f"Log level set to {level}", LogLevel.INFO, "logging") + + async def notify(self, data: Any, level: LogLevel, logger_name: Optional[str] = None) -> None: + """Send log notification to subscribers. + + Args: + data: Log message data + level: Log severity level + logger_name: Optional logger name + """ + # Skip if below current level + if not self._should_log(level): + return + + # Format notification message + message = { + "type": "log", + "data": { + "level": level, + "data": data, + "timestamp": datetime.utcnow().isoformat(), + }, + } + if logger_name: + message["data"]["logger"] = logger_name + + # Log through standard logging + logger = self.get_logger(logger_name or "") + log_func = getattr(logger, level.lower()) + log_func(data) + + # Notify subscribers + for queue in self._subscribers: + try: + await queue.put(message) + except Exception as e: + logger.error(f"Failed to notify subscriber: {e}") + + async def subscribe(self) -> AsyncGenerator[Dict[str, Any], None]: + """Subscribe to log messages. + + Returns a generator yielding log message events. + + Yields: + Log message events + """ + queue: asyncio.Queue = asyncio.Queue() + self._subscribers.append(queue) + try: + while True: + message = await queue.get() + yield message + finally: + self._subscribers.remove(queue) + + def _should_log(self, level: LogLevel) -> bool: + """Check if level meets minimum threshold. + + Args: + level: Log level to check + + Returns: + True if should log + """ + level_values = { + LogLevel.DEBUG: 0, + LogLevel.INFO: 1, + LogLevel.NOTICE: 2, + LogLevel.WARNING: 3, + LogLevel.ERROR: 4, + LogLevel.CRITICAL: 5, + LogLevel.ALERT: 6, + LogLevel.EMERGENCY: 7, + } + + return level_values[level] >= level_values[self._level] diff --git a/mcpgateway/services/prompt_service.py b/mcpgateway/services/prompt_service.py new file mode 100644 index 0000000000..a4b9adca4f --- /dev/null +++ b/mcpgateway/services/prompt_service.py @@ -0,0 +1,740 @@ +# -*- coding: utf-8 -*- +"""Prompt Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements prompt template management according to the MCP specification. +It handles: +- Prompt template registration and retrieval +- Prompt argument validation +- Template rendering with arguments +- Resource embedding in prompts +- Active/inactive prompt management +""" + +import asyncio +import logging +from datetime import datetime +from string import Formatter +from typing import Any, AsyncGenerator, Dict, List, Optional, Set + +from jinja2 import Environment, meta, select_autoescape +from sqlalchemy import delete, func, not_, select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session + +from mcpgateway.db import Prompt as DbPrompt +from mcpgateway.db import PromptMetric, server_prompt_association +from mcpgateway.schemas import PromptCreate, PromptRead, PromptUpdate +from mcpgateway.types import Message, PromptResult, Role, TextContent + +logger = logging.getLogger(__name__) + + +class PromptError(Exception): + """Base class for prompt-related errors.""" + + +class PromptNotFoundError(PromptError): + """Raised when a requested prompt is not found.""" + + +class PromptNameConflictError(PromptError): + """Raised when a prompt name conflicts with existing (active or inactive) prompt.""" + + def __init__(self, name: str, is_active: bool = True, prompt_id: Optional[int] = None): + """Initialize the error with prompt information. + + Args: + name: The conflicting prompt name + is_active: Whether the existing prompt is active + prompt_id: ID of the existing prompt if available + """ + self.name = name + self.is_active = is_active + self.prompt_id = prompt_id + message = f"Prompt already exists with name: {name}" + if not is_active: + message += f" (currently inactive, ID: {prompt_id})" + super().__init__(message) + + +class PromptValidationError(PromptError): + """Raised when prompt validation fails.""" + + +class PromptService: + """Service for managing prompt templates. + + Handles: + - Template registration and retrieval + - Argument validation + - Template rendering + - Resource embedding + - Active/inactive status management + """ + + def __init__(self) -> None: + """ + Initialize the prompt service. + + Sets up the Jinja2 environment for rendering prompt templates. + Although these templates are rendered as JSON for the API, if the output is ever + embedded into an HTML page, unescaped content could be exploited for cross-site scripting (XSS) attacks. + Enabling autoescaping for 'html' and 'xml' templates via select_autoescape helps mitigate this risk. + """ + self._event_subscribers: List[asyncio.Queue] = [] + self._jinja_env = Environment(autoescape=select_autoescape(["html", "xml"]), trim_blocks=True, lstrip_blocks=True) + + async def initialize(self) -> None: + """Initialize the service.""" + logger.info("Initializing prompt service") + + async def shutdown(self) -> None: + """Shutdown the service.""" + self._event_subscribers.clear() + logger.info("Prompt service shutdown complete") + + def _convert_db_prompt(self, db_prompt: DbPrompt) -> Dict[str, Any]: + """ + Convert a DbPrompt instance to a dictionary matching the PromptRead schema, + including aggregated metrics computed from the associated PromptMetric records. + + Args: + db_prompt: Db prompt to convert + + Returns: + dict: Dictionary matching the PromptRead schema + """ + arg_schema = db_prompt.argument_schema or {} + properties = arg_schema.get("properties", {}) + required_list = arg_schema.get("required", []) + arguments_list = [] + for arg_name, prop in properties.items(): + arguments_list.append( + { + "name": arg_name, + "description": prop.get("description") or "", + "required": arg_name in required_list, + } + ) + total = len(db_prompt.metrics) if hasattr(db_prompt, "metrics") and db_prompt.metrics is not None else 0 + successful = sum(1 for m in db_prompt.metrics if m.is_success) if total > 0 else 0 + failed = sum(1 for m in db_prompt.metrics if not m.is_success) if total > 0 else 0 + failure_rate = failed / total if total > 0 else 0.0 + min_rt = min((m.response_time for m in db_prompt.metrics), default=None) if total > 0 else None + max_rt = max((m.response_time for m in db_prompt.metrics), default=None) if total > 0 else None + avg_rt = (sum(m.response_time for m in db_prompt.metrics) / total) if total > 0 else None + last_time = max((m.timestamp for m in db_prompt.metrics), default=None) if total > 0 else None + + return { + "id": db_prompt.id, + "name": db_prompt.name, + "description": db_prompt.description, + "template": db_prompt.template, + "arguments": arguments_list, + "created_at": db_prompt.created_at, + "updated_at": db_prompt.updated_at, + "is_active": db_prompt.is_active, + "metrics": { + "totalExecutions": total, + "successfulExecutions": successful, + "failedExecutions": failed, + "failureRate": failure_rate, + "minResponseTime": min_rt, + "maxResponseTime": max_rt, + "avgResponseTime": avg_rt, + "lastExecutionTime": last_time, + }, + } + + async def register_prompt(self, db: Session, prompt: PromptCreate) -> PromptRead: + """Register a new prompt template. + + Args: + db: Database session + prompt: Prompt creation schema + + Returns: + Created prompt information + + Raises: + PromptNameConflictError: If prompt name already exists + PromptError: For other prompt registration errors + """ + try: + # Check for name conflicts (both active and inactive) + existing_prompt = db.execute(select(DbPrompt).where(DbPrompt.name == prompt.name)).scalar_one_or_none() + + if existing_prompt: + raise PromptNameConflictError( + prompt.name, + is_active=existing_prompt.is_active, + prompt_id=existing_prompt.id, + ) + + # Validate template syntax + self._validate_template(prompt.template) + + # Extract required arguments from template + required_args = self._get_required_arguments(prompt.template) + + # Create argument schema + argument_schema = { + "type": "object", + "properties": {}, + "required": list(required_args), + } + for arg in prompt.arguments: + schema = {"type": "string"} + if arg.description is not None: + schema["description"] = arg.description + argument_schema["properties"][arg.name] = schema + + # Create DB model + db_prompt = DbPrompt( + name=prompt.name, + description=prompt.description, + template=prompt.template, + argument_schema=argument_schema, + ) + + # Add to DB + db.add(db_prompt) + db.commit() + db.refresh(db_prompt) + + # Notify subscribers + await self._notify_prompt_added(db_prompt) + + logger.info(f"Registered prompt: {prompt.name}") + prompt_dict = self._convert_db_prompt(db_prompt) + return PromptRead.model_validate(prompt_dict) + + except IntegrityError: + db.rollback() + raise PromptError(f"Prompt already exists: {prompt.name}") + except Exception as e: + db.rollback() + raise PromptError(f"Failed to register prompt: {str(e)}") + + async def list_prompts(self, db: Session, include_inactive: bool = False, cursor: Optional[str] = None) -> List[PromptRead]: + """ + Retrieve a list of prompt templates from the database. + + This method retrieves prompt templates from the database and converts them into a list + of PromptRead objects. It supports filtering out inactive prompts based on the + include_inactive parameter. The cursor parameter is reserved for future pagination support + but is currently not implemented. + + Args: + db (Session): The SQLAlchemy database session. + include_inactive (bool): If True, include inactive prompts in the result. + Defaults to False. + cursor (Optional[str], optional): An opaque cursor token for pagination. Currently, + this parameter is ignored. Defaults to None. + + Returns: + List[PromptRead]: A list of prompt templates represented as PromptRead objects. + """ + query = select(DbPrompt) + if not include_inactive: + query = query.where(DbPrompt.is_active) + # Cursor-based pagination logic can be implemented here in the future. + logger.debug(cursor) + prompts = db.execute(query).scalars().all() + return [PromptRead.model_validate(self._convert_db_prompt(p)) for p in prompts] + + async def list_server_prompts(self, db: Session, server_id: int, include_inactive: bool = False, cursor: Optional[str] = None) -> List[PromptRead]: + """ + Retrieve a list of prompt templates from the database. + + This method retrieves prompt templates from the database and converts them into a list + of PromptRead objects. It supports filtering out inactive prompts based on the + include_inactive parameter. The cursor parameter is reserved for future pagination support + but is currently not implemented. + + Args: + db (Session): The SQLAlchemy database session. + server_id (int): Server ID + include_inactive (bool): If True, include inactive prompts in the result. + Defaults to False. + cursor (Optional[str], optional): An opaque cursor token for pagination. Currently, + this parameter is ignored. Defaults to None. + + Returns: + List[PromptRead]: A list of prompt templates represented as PromptRead objects. + """ + query = select(DbPrompt).join(server_prompt_association, DbPrompt.id == server_prompt_association.c.prompt_id).where(server_prompt_association.c.server_id == server_id) + if not include_inactive: + query = query.where(DbPrompt.is_active) + # Cursor-based pagination logic can be implemented here in the future. + logger.debug(cursor) + prompts = db.execute(query).scalars().all() + return [PromptRead.model_validate(self._convert_db_prompt(p)) for p in prompts] + + async def get_prompt(self, db: Session, name: str, arguments: Optional[Dict[str, str]] = None) -> PromptResult: + """Get a prompt template and optionally render it. + + Args: + db: Database session + name: Name of prompt to get + arguments: Optional arguments for rendering + + Returns: + Prompt result with rendered messages + + Raises: + PromptNotFoundError: If prompt not found + PromptError: For other prompt errors + """ + # Find prompt + prompt = db.execute(select(DbPrompt).where(DbPrompt.name == name).where(DbPrompt.is_active)).scalar_one_or_none() + + if not prompt: + inactive_prompt = db.execute(select(DbPrompt).where(DbPrompt.name == name).where(not_(DbPrompt.is_active))).scalar_one_or_none() + if inactive_prompt: + raise PromptNotFoundError(f"Prompt '{name}' exists but is inactive") + + raise PromptNotFoundError(f"Prompt not found: {name}") + + if not arguments: + return PromptResult( + messages=[ + Message( + role=Role.USER, + content=TextContent(type="text", text=prompt.template), + ) + ], + description=prompt.description, + ) + + try: + prompt.validate_arguments(arguments) + rendered = self._render_template(prompt.template, arguments) + messages = self._parse_messages(rendered) + return PromptResult(messages=messages, description=prompt.description) + except Exception as e: + raise PromptError(f"Failed to process prompt: {str(e)}") + + async def update_prompt(self, db: Session, name: str, prompt_update: PromptUpdate) -> PromptRead: + """Update an existing prompt. + + Args: + db: Database session + name: Name of prompt to update + prompt_update: Updated prompt data + + Returns: + Updated prompt information + + Raises: + PromptNotFoundError: If prompt not found + PromptError: For other update errors + PromptNameConflictError: When prompt name conflict happens + """ + try: + prompt = db.execute(select(DbPrompt).where(DbPrompt.name == name).where(DbPrompt.is_active)).scalar_one_or_none() + if not prompt: + inactive_prompt = db.execute(select(DbPrompt).where(DbPrompt.name == name).where(not_(DbPrompt.is_active))).scalar_one_or_none() + if inactive_prompt: + raise PromptNotFoundError(f"Prompt '{name}' exists but is inactive") + + raise PromptNotFoundError(f"Prompt not found: {name}") + + if prompt_update.name is not None and prompt_update.name != prompt.name: + existing_prompt = db.execute(select(DbPrompt).where(DbPrompt.name == prompt_update.name).where(DbPrompt.id != prompt.id)).scalar_one_or_none() + if existing_prompt: + raise PromptNameConflictError( + prompt_update.name, + is_active=existing_prompt.is_active, + prompt_id=existing_prompt.id, + ) + + if prompt_update.name is not None: + prompt.name = prompt_update.name + if prompt_update.description is not None: + prompt.description = prompt_update.description + if prompt_update.template is not None: + prompt.template = prompt_update.template + self._validate_template(prompt.template) + if prompt_update.arguments is not None: + required_args = self._get_required_arguments(prompt.template) + argument_schema = { + "type": "object", + "properties": {}, + "required": list(required_args), + } + for arg in prompt_update.arguments: + schema = {"type": "string"} + if arg.description is not None: + schema["description"] = arg.description + argument_schema["properties"][arg.name] = schema + prompt.argument_schema = argument_schema + + prompt.updated_at = datetime.utcnow() + db.commit() + db.refresh(prompt) + + await self._notify_prompt_updated(prompt) + return PromptRead.model_validate(self._convert_db_prompt(prompt)) + + except Exception as e: + db.rollback() + raise PromptError(f"Failed to update prompt: {str(e)}") + + async def toggle_prompt_status(self, db: Session, prompt_id: int, activate: bool) -> PromptRead: + """Toggle prompt active status. + + Args: + db: Database session + prompt_id: Prompt ID to toggle + activate: True to activate, False to deactivate + + Returns: + Updated prompt information + + Raises: + PromptNotFoundError: If prompt not found + PromptError: For other errors + """ + try: + prompt = db.get(DbPrompt, prompt_id) + if not prompt: + raise PromptNotFoundError(f"Prompt not found: {prompt_id}") + if prompt.is_active != activate: + prompt.is_active = activate + prompt.updated_at = datetime.utcnow() + db.commit() + db.refresh(prompt) + if activate: + await self._notify_prompt_activated(prompt) + else: + await self._notify_prompt_deactivated(prompt) + logger.info(f"Prompt {prompt.name} {'activated' if activate else 'deactivated'}") + return PromptRead.model_validate(self._convert_db_prompt(prompt)) + except Exception as e: + db.rollback() + raise PromptError(f"Failed to toggle prompt status: {str(e)}") + + # Get prompt details for admin ui + async def get_prompt_details(self, db: Session, name: str, include_inactive: bool = False) -> Dict[str, Any]: + """Get prompt details for admin UI. + + Args: + db: Database session + name: Name of prompt + include_inactive: Whether to include inactive prompts + + Returns: + Prompt details + + Raises: + PromptNotFoundError: If prompt not found + """ + query = select(DbPrompt).where(DbPrompt.name == name) + if not include_inactive: + query = query.where(DbPrompt.is_active) + prompt = db.execute(query).scalar_one_or_none() + if not prompt: + if not include_inactive: + inactive_prompt = db.execute(select(DbPrompt).where(DbPrompt.name == name).where(not_(DbPrompt.is_active))).scalar_one_or_none() + if inactive_prompt: + raise PromptNotFoundError(f"Prompt '{name}' exists but is inactive") + raise PromptNotFoundError(f"Prompt not found: {name}") + # Return the fully converted prompt including metrics + return self._convert_db_prompt(prompt) + + async def delete_prompt(self, db: Session, name: str) -> None: + """Permanently delete a registered prompt. + + Args: + db: Database session + name: Name of prompt to delete + + Raises: + PromptNotFoundError: If prompt not found + PromptError: For other deletion errors + Exception: If prompt not found + """ + try: + prompt = db.execute(select(DbPrompt).where(DbPrompt.name == name)).scalar_one_or_none() + if not prompt: + raise PromptNotFoundError(f"Prompt not found: {name}") + prompt_info = {"id": prompt.id, "name": prompt.name} + db.delete(prompt) + db.commit() + await self._notify_prompt_deleted(prompt_info) + logger.info(f"Permanently deleted prompt: {name}") + except Exception as e: + db.rollback() + if isinstance(e, PromptNotFoundError): + raise e + raise PromptError(f"Failed to delete prompt: {str(e)}") + + async def subscribe_events(self) -> AsyncGenerator[Dict[str, Any], None]: + """Subscribe to prompt events. + + Yields: + Prompt event messages + """ + queue: asyncio.Queue = asyncio.Queue() + self._event_subscribers.append(queue) + try: + while True: + event = await queue.get() + yield event + finally: + self._event_subscribers.remove(queue) + + def _validate_template(self, template: str) -> None: + """Validate template syntax. + + Args: + template: Template to validate + + Raises: + PromptValidationError: If template is invalid + """ + try: + self._jinja_env.parse(template) + except Exception as e: + raise PromptValidationError(f"Invalid template syntax: {str(e)}") + + def _get_required_arguments(self, template: str) -> Set[str]: + """Extract required arguments from template. + + Args: + template: Template to analyze + + Returns: + Set of required argument names + """ + ast = self._jinja_env.parse(template) + variables = meta.find_undeclared_variables(ast) + formatter = Formatter() + format_vars = {field_name for _, field_name, _, _ in formatter.parse(template) if field_name is not None} + return variables.union(format_vars) + + def _render_template(self, template: str, arguments: Dict[str, str]) -> str: + """Render template with arguments. + + Args: + template: Template to render + arguments: Arguments for rendering + + Returns: + Rendered template text + + Raises: + PromptError: If rendering fails + """ + try: + jinja_template = self._jinja_env.from_string(template) + return jinja_template.render(**arguments) + except Exception: + try: + return template.format(**arguments) + except Exception as e: + raise PromptError(f"Failed to render template: {str(e)}") + + def _parse_messages(self, text: str) -> List[Message]: + """Parse rendered text into messages. + + Args: + text: Text to parse + + Returns: + List of parsed messages + """ + messages = [] + current_role = Role.USER + current_text = [] + for line in text.split("\n"): + if line.startswith("# Assistant:"): + if current_text: + messages.append( + Message( + role=current_role, + content=TextContent(type="text", text="\n".join(current_text).strip()), + ) + ) + current_role = Role.ASSISTANT + current_text = [] + elif line.startswith("# User:"): + if current_text: + messages.append( + Message( + role=current_role, + content=TextContent(type="text", text="\n".join(current_text).strip()), + ) + ) + current_role = Role.USER + current_text = [] + else: + current_text.append(line) + if current_text: + messages.append( + Message( + role=current_role, + content=TextContent(type="text", text="\n".join(current_text).strip()), + ) + ) + return messages + + async def _notify_prompt_added(self, prompt: DbPrompt) -> None: + """ + Notify subscribers of prompt addition. + + Args: + prompt: Prompt to add + """ + event = { + "type": "prompt_added", + "data": { + "id": prompt.id, + "name": prompt.name, + "description": prompt.description, + "is_active": prompt.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_prompt_updated(self, prompt: DbPrompt) -> None: + """ + Notify subscribers of prompt update. + + Args: + prompt: Prompt to update + """ + event = { + "type": "prompt_updated", + "data": { + "id": prompt.id, + "name": prompt.name, + "description": prompt.description, + "is_active": prompt.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_prompt_activated(self, prompt: DbPrompt) -> None: + """ + Notify subscribers of prompt activation. + + Args: + prompt: Prompt to activate + """ + event = { + "type": "prompt_activated", + "data": {"id": prompt.id, "name": prompt.name, "is_active": True}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_prompt_deactivated(self, prompt: DbPrompt) -> None: + """ + Notify subscribers of prompt deactivation. + + Args: + prompt: Prompt to deactivate + """ + event = { + "type": "prompt_deactivated", + "data": {"id": prompt.id, "name": prompt.name, "is_active": False}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_prompt_deleted(self, prompt_info: Dict[str, Any]) -> None: + """ + Notify subscribers of prompt deletion. + + Args: + prompt_info: Dict on prompt to notify as deleted + """ + event = { + "type": "prompt_deleted", + "data": prompt_info, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_prompt_removed(self, prompt: DbPrompt) -> None: + """ + Notify subscribers of prompt removal (deactivation). + + Args: + prompt: Prompt to remove + """ + event = { + "type": "prompt_removed", + "data": {"id": prompt.id, "name": prompt.name, "is_active": False}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _publish_event(self, event: Dict[str, Any]) -> None: + """ + Publish event to all subscribers. + + Args: + event: Dictionary containing event info + """ + for queue in self._event_subscribers: + await queue.put(event) + + # --- Metrics --- + async def aggregate_metrics(self, db: Session) -> Dict[str, Any]: + """ + Aggregate metrics for all prompt invocations. + + Args: + db: Database Session + + Returns: + Dict[str, Any]: Aggregated prompt metrics with keys: + - total_executions + - successful_executions + - failed_executions + - failure_rate + - min_response_time + - max_response_time + - avg_response_time + - last_execution_time + """ + + total = db.execute(select(func.count(PromptMetric.id))).scalar() or 0 # pylint: disable=not-callable + successful = db.execute(select(func.count(PromptMetric.id)).where(PromptMetric.is_success)).scalar() or 0 # pylint: disable=not-callable + failed = db.execute(select(func.count(PromptMetric.id)).where(not_(PromptMetric.is_success))).scalar() or 0 # pylint: disable=not-callable + failure_rate = failed / total if total > 0 else 0.0 + min_rt = db.execute(select(func.min(PromptMetric.response_time))).scalar() + max_rt = db.execute(select(func.max(PromptMetric.response_time))).scalar() + avg_rt = db.execute(select(func.avg(PromptMetric.response_time))).scalar() + last_time = db.execute(select(func.max(PromptMetric.timestamp))).scalar() + + return { + "total_executions": total, + "successful_executions": successful, + "failed_executions": failed, + "failure_rate": failure_rate, + "min_response_time": min_rt, + "max_response_time": max_rt, + "avg_response_time": avg_rt, + "last_execution_time": last_time, + } + + async def reset_metrics(self, db: Session) -> None: + """ + Reset all prompt metrics by deleting all records from the prompt metrics table. + + Args: + db: Database Session + """ + + db.execute(delete(PromptMetric)) + db.commit() diff --git a/mcpgateway/services/resource_service.py b/mcpgateway/services/resource_service.py new file mode 100644 index 0000000000..422b03be49 --- /dev/null +++ b/mcpgateway/services/resource_service.py @@ -0,0 +1,875 @@ +# -*- coding: utf-8 -*- +"""Resource Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements resource management according to the MCP specification. +It handles: +- Resource registration and retrieval +- Resource templates and URI handling +- Resource subscriptions and updates +- Content type management +- Active/inactive resource management +""" + +import asyncio +import logging +import mimetypes +import re +from datetime import datetime +from typing import Any, AsyncGenerator, Dict, List, Optional, Union +from urllib.parse import urlparse + +import parse +from sqlalchemy import delete, func, not_, select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session + +from mcpgateway.db import Resource as DbResource +from mcpgateway.db import ResourceMetric +from mcpgateway.db import ResourceSubscription as DbSubscription +from mcpgateway.db import server_resource_association +from mcpgateway.schemas import ( + ResourceCreate, + ResourceMetrics, + ResourceRead, + ResourceSubscription, + ResourceUpdate, +) +from mcpgateway.types import ResourceContent, ResourceTemplate, TextContent + +logger = logging.getLogger(__name__) + + +class ResourceError(Exception): + """Base class for resource-related errors.""" + + +class ResourceNotFoundError(ResourceError): + """Raised when a requested resource is not found.""" + + +class ResourceURIConflictError(ResourceError): + """Raised when a resource URI conflicts with existing (active or inactive) resource.""" + + def __init__(self, uri: str, is_active: bool = True, resource_id: Optional[int] = None): + """Initialize the error with resource information. + + Args: + uri: The conflicting resource URI + is_active: Whether the existing resource is active + resource_id: ID of the existing resource if available + """ + self.uri = uri + self.is_active = is_active + self.resource_id = resource_id + message = f"Resource already exists with URI: {uri}" + if not is_active: + message += f" (currently inactive, ID: {resource_id})" + super().__init__(message) + + +class ResourceValidationError(ResourceError): + """Raised when resource validation fails.""" + + +class ResourceService: + """Service for managing resources. + + Handles: + - Resource registration and retrieval + - Resource templates and URIs + - Resource subscriptions + - Content type detection + - Active/inactive status management + """ + + def __init__(self): + """Initialize the resource service.""" + self._event_subscribers: Dict[str, List[asyncio.Queue]] = {} + self._template_cache: Dict[str, ResourceTemplate] = {} + + # Initialize mime types + mimetypes.init() + + async def initialize(self) -> None: + """Initialize the service.""" + logger.info("Initializing resource service") + + async def shutdown(self) -> None: + """Shutdown the service.""" + # Clear subscriptions + self._event_subscribers.clear() + logger.info("Resource service shutdown complete") + + def _convert_resource_to_read(self, resource: DbResource) -> ResourceRead: + """ + Converts a DbResource instance into a ResourceRead model, including aggregated metrics. + + Args: + resource (DbResource): The ORM instance of the resource. + + Returns: + ResourceRead: The Pydantic model representing the resource, including aggregated metrics. + """ + resource_dict = resource.__dict__.copy() + # Remove SQLAlchemy state and any pre-existing 'metrics' attribute + resource_dict.pop("_sa_instance_state", None) + resource_dict.pop("metrics", None) + + # Compute aggregated metrics from the resource's metrics list. + total = len(resource.metrics) if hasattr(resource, "metrics") and resource.metrics is not None else 0 + successful = sum(1 for m in resource.metrics if m.is_success) if total > 0 else 0 + failed = sum(1 for m in resource.metrics if not m.is_success) if total > 0 else 0 + failure_rate = (failed / total) if total > 0 else 0.0 + min_rt = min((m.response_time for m in resource.metrics), default=None) if total > 0 else None + max_rt = max((m.response_time for m in resource.metrics), default=None) if total > 0 else None + avg_rt = (sum(m.response_time for m in resource.metrics) / total) if total > 0 else None + last_time = max((m.timestamp for m in resource.metrics), default=None) if total > 0 else None + + resource_dict["metrics"] = { + "total_executions": total, + "successful_executions": successful, + "failed_executions": failed, + "failure_rate": failure_rate, + "min_response_time": min_rt, + "max_response_time": max_rt, + "avg_response_time": avg_rt, + "last_execution_time": last_time, + } + return ResourceRead.model_validate(resource_dict) + + async def register_resource(self, db: Session, resource: ResourceCreate) -> ResourceRead: + """Register a new resource. + + Args: + db: Database session + resource: Resource creation schema + + Returns: + Created resource information + + Raises: + ResourceURIConflictError: If resource URI already exists + ResourceValidationError: If resource validation fails + ResourceError: For other resource registration errors + """ + try: + # Check for URI conflicts (both active and inactive) + existing_resource = db.execute(select(DbResource).where(DbResource.uri == resource.uri)).scalar_one_or_none() + + if existing_resource: + raise ResourceURIConflictError( + resource.uri, + is_active=existing_resource.is_active, + resource_id=existing_resource.id, + ) + + # Validate URI + if not self._is_valid_uri(resource.uri): + raise ResourceValidationError(f"Invalid URI: {resource.uri}") + + # Detect mime type if not provided + mime_type = resource.mime_type + if not mime_type: + mime_type = self._detect_mime_type(resource.uri, resource.content) + + # Determine content storage + is_text = mime_type and mime_type.startswith("text/") or isinstance(resource.content, str) + + # Create DB model + db_resource = DbResource( + uri=resource.uri, + name=resource.name, + description=resource.description, + mime_type=mime_type, + template=resource.template, + text_content=resource.content if is_text else None, + binary_content=(resource.content.encode() if is_text and isinstance(resource.content, str) else resource.content if isinstance(resource.content, bytes) else None), + size=len(resource.content) if resource.content else 0, + ) + + # Add to DB + db.add(db_resource) + db.commit() + db.refresh(db_resource) + + # Notify subscribers + await self._notify_resource_added(db_resource) + + logger.info(f"Registered resource: {resource.uri}") + return self._convert_resource_to_read(db_resource) + + except IntegrityError: + db.rollback() + raise ResourceError(f"Resource already exists: {resource.uri}") + except Exception as e: + db.rollback() + raise ResourceError(f"Failed to register resource: {str(e)}") + + async def list_resources(self, db: Session, include_inactive: bool = False) -> List[ResourceRead]: + """ + Retrieve a list of registered resources from the database. + + This method retrieves resources from the database and converts them into a list + of ResourceRead objects. It supports filtering out inactive resources based on the + include_inactive parameter. The cursor parameter is reserved for future pagination support + but is currently not implemented. + + Args: + db (Session): The SQLAlchemy database session. + include_inactive (bool): If True, include inactive resources in the result. + Defaults to False. + cursor (Optional[str], optional): An opaque cursor token for pagination. Currently, + this parameter is ignored. Defaults to None. + + Returns: + List[ResourceRead]: A list of resources represented as ResourceRead objects. + """ + query = select(DbResource) + if not include_inactive: + query = query.where(DbResource.is_active) + # Cursor-based pagination logic can be implemented here in the future. + resources = db.execute(query).scalars().all() + return [self._convert_resource_to_read(r) for r in resources] + + async def list_server_resources(self, db: Session, server_id: int, include_inactive: bool = False) -> List[ResourceRead]: + """ + Retrieve a list of registered resources from the database. + + This method retrieves resources from the database and converts them into a list + of ResourceRead objects. It supports filtering out inactive resources based on the + include_inactive parameter. The cursor parameter is reserved for future pagination support + but is currently not implemented. + + Args: + db (Session): The SQLAlchemy database session. + server_id (int): Server ID + include_inactive (bool): If True, include inactive resources in the result. + Defaults to False. + cursor (Optional[str], optional): An opaque cursor token for pagination. Currently, + this parameter is ignored. Defaults to None. + + Returns: + List[ResourceRead]: A list of resources represented as ResourceRead objects. + """ + query = select(DbResource).join(server_resource_association, DbResource.id == server_resource_association.c.resource_id).where(server_resource_association.c.server_id == server_id) + if not include_inactive: + query = query.where(DbResource.is_active) + # Cursor-based pagination logic can be implemented here in the future. + resources = db.execute(query).scalars().all() + return [self._convert_resource_to_read(r) for r in resources] + + async def read_resource(self, db: Session, uri: str) -> ResourceContent: + """Read a resource's content. + + Args: + db: Database session + uri: Resource URI to read + + Returns: + Resource content object + + Raises: + ResourceNotFoundError: If resource not found + """ + # Check for template + if "{" in uri and "}" in uri: + return await self._read_template_resource(uri) + + # Find resource + resource = db.execute(select(DbResource).where(DbResource.uri == uri).where(DbResource.is_active)).scalar_one_or_none() + + if not resource: + # Check if inactive resource exists + inactive_resource = db.execute(select(DbResource).where(DbResource.uri == uri).where(not_(DbResource.is_active))).scalar_one_or_none() + + if inactive_resource: + raise ResourceNotFoundError(f"Resource '{uri}' exists but is inactive") + + raise ResourceNotFoundError(f"Resource not found: {uri}") + + # Return content + return resource.content + + async def toggle_resource_status(self, db: Session, resource_id: int, activate: bool) -> ResourceRead: + """Toggle resource active status. + + Args: + db: Database session + resource_id: Resource ID to toggle + activate: True to activate, False to deactivate + + Returns: + Updated resource information + + Raises: + ResourceNotFoundError: If resource not found + ResourceError: For other errors + """ + try: + resource = db.get(DbResource, resource_id) + if not resource: + raise ResourceNotFoundError(f"Resource not found: {resource_id}") + + # Update status if it's different + if resource.is_active != activate: + resource.is_active = activate + resource.updated_at = datetime.utcnow() + db.commit() + db.refresh(resource) + + # Notify subscribers + if activate: + await self._notify_resource_activated(resource) + else: + await self._notify_resource_deactivated(resource) + + logger.info(f"Resource {resource.uri} {'activated' if activate else 'deactivated'}") + + return self._convert_resource_to_read(resource) + + except Exception as e: + db.rollback() + raise ResourceError(f"Failed to toggle resource status: {str(e)}") + + async def subscribe_resource(self, db: Session, subscription: ResourceSubscription) -> None: + """Subscribe to resource updates. + + Args: + db: Database session + subscription: Subscription details + + Raises: + ResourceNotFoundError: If resource not found + ResourceError: For other subscription errors + """ + try: + # Verify resource exists + resource = db.execute(select(DbResource).where(DbResource.uri == subscription.uri).where(DbResource.is_active)).scalar_one_or_none() + + if not resource: + # Check if inactive resource exists + inactive_resource = db.execute(select(DbResource).where(DbResource.uri == subscription.uri).where(not_(DbResource.is_active))).scalar_one_or_none() + + if inactive_resource: + raise ResourceNotFoundError(f"Resource '{subscription.uri}' exists but is inactive") + + raise ResourceNotFoundError(f"Resource not found: {subscription.uri}") + + # Create subscription + db_sub = DbSubscription(resource_id=resource.id, subscriber_id=subscription.subscriber_id) + db.add(db_sub) + db.commit() + + logger.info(f"Added subscription for {subscription.uri} by {subscription.subscriber_id}") + + except Exception as e: + db.rollback() + raise ResourceError(f"Failed to subscribe: {str(e)}") + + async def unsubscribe_resource(self, db: Session, subscription: ResourceSubscription) -> None: + """Unsubscribe from resource updates. + + Args: + db: Database session + subscription: Subscription to remove + """ + try: + # Find resource + resource = db.execute(select(DbResource).where(DbResource.uri == subscription.uri)).scalar_one_or_none() + + if not resource: + return + + # Remove subscription + db.execute(select(DbSubscription).where(DbSubscription.resource_id == resource.id).where(DbSubscription.subscriber_id == subscription.subscriber_id)).delete() + db.commit() + + logger.info(f"Removed subscription for {subscription.uri} by {subscription.subscriber_id}") + + except Exception as e: + db.rollback() + logger.error(f"Failed to unsubscribe: {str(e)}") + + async def update_resource(self, db: Session, uri: str, resource_update: ResourceUpdate) -> ResourceRead: + """Update a resource. + + Args: + db: Database session + uri: Resource URI to update + resource_update: Updated resource data + + Returns: + Updated resource information + + Raises: + ResourceNotFoundError: If resource not found + ResourceError: For other update errors + Exception: If resource not found + """ + try: + # Find resource + resource = db.execute(select(DbResource).where(DbResource.uri == uri).where(DbResource.is_active)).scalar_one_or_none() + + if not resource: + # Check if inactive resource exists + inactive_resource = db.execute(select(DbResource).where(DbResource.uri == uri).where(not_(DbResource.is_active))).scalar_one_or_none() + + if inactive_resource: + raise ResourceNotFoundError(f"Resource '{uri}' exists but is inactive") + + raise ResourceNotFoundError(f"Resource not found: {uri}") + + # Update fields if provided + if resource_update.name is not None: + resource.name = resource_update.name + if resource_update.description is not None: + resource.description = resource_update.description + if resource_update.mime_type is not None: + resource.mime_type = resource_update.mime_type + if resource_update.template is not None: + resource.template = resource_update.template + + # Update content if provided + if resource_update.content is not None: + # Determine content storage + is_text = resource.mime_type and resource.mime_type.startswith("text/") or isinstance(resource_update.content, str) + + resource.text_content = resource_update.content if is_text else None + resource.binary_content = ( + resource_update.content.encode() if is_text and isinstance(resource_update.content, str) else resource_update.content if isinstance(resource_update.content, bytes) else None + ) + resource.size = len(resource_update.content) + + resource.updated_at = datetime.utcnow() + db.commit() + db.refresh(resource) + + # Notify subscribers + await self._notify_resource_updated(resource) + + logger.info(f"Updated resource: {uri}") + return self._convert_resource_to_read(resource) + + except Exception as e: + db.rollback() + if isinstance(e, ResourceNotFoundError): + raise e + raise ResourceError(f"Failed to update resource: {str(e)}") + + async def delete_resource(self, db: Session, uri: str) -> None: + """Permanently delete a resource. + + Args: + db: Database session + uri: Resource URI to delete + + Raises: + ResourceNotFoundError: If resource not found + ResourceError: For other deletion errors + """ + try: + # Find resource by its URI. + resource = db.execute(select(DbResource).where(DbResource.uri == uri)).scalar_one_or_none() + + if not resource: + # If resource doesn't exist, rollback and re-raise a ResourceNotFoundError. + db.rollback() + raise ResourceNotFoundError(f"Resource not found: {uri}") + + # Store resource info for notification before deletion. + resource_info = { + "id": resource.id, + "uri": resource.uri, + "name": resource.name, + } + + # Remove subscriptions using SQLAlchemy's delete() expression. + db.execute(delete(DbSubscription).where(DbSubscription.resource_id == resource.id)) + + # Hard delete the resource. + db.delete(resource) + db.commit() + + # Notify subscribers. + await self._notify_resource_deleted(resource_info) + + logger.info(f"Permanently deleted resource: {uri}") + + except ResourceNotFoundError: + # ResourceNotFoundError is re-raised to be handled in the endpoint. + raise + except Exception as e: + db.rollback() + raise ResourceError(f"Failed to delete resource: {str(e)}") + + async def get_resource_by_uri(self, db: Session, uri: str, include_inactive: bool = False) -> ResourceRead: + """Get resource by URI. + + Args: + db: Database session + uri: Resource URI + include_inactive: Whether to include inactive resources + + Returns: + Resource information + + Raises: + ResourceNotFoundError: If resource not found + """ + query = select(DbResource).where(DbResource.uri == uri) + + if not include_inactive: + query = query.where(DbResource.is_active) + + resource = db.execute(query).scalar_one_or_none() + + if not resource: + if not include_inactive: + # Check if inactive resource exists + inactive_resource = db.execute(select(DbResource).where(DbResource.uri == uri).where(not_(DbResource.is_active))).scalar_one_or_none() + + if inactive_resource: + raise ResourceNotFoundError(f"Resource '{uri}' exists but is inactive") + + raise ResourceNotFoundError(f"Resource not found: {uri}") + + return self._convert_resource_to_read(resource) + + async def _notify_resource_activated(self, resource: DbResource) -> None: + """ + Notify subscribers of resource activation. + + Args: + resource: Resource to activate + """ + event = { + "type": "resource_activated", + "data": { + "id": resource.id, + "uri": resource.uri, + "name": resource.name, + "is_active": True, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(resource.uri, event) + + async def _notify_resource_deactivated(self, resource: DbResource) -> None: + """ + Notify subscribers of resource deactivation. + + Args: + resource: Resource to deactivate + """ + event = { + "type": "resource_deactivated", + "data": { + "id": resource.id, + "uri": resource.uri, + "name": resource.name, + "is_active": False, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(resource.uri, event) + + async def _notify_resource_deleted(self, resource_info: Dict[str, Any]) -> None: + """ + Notify subscribers of resource deletion. + + Args: + resource_info: Dictionary of resource to delete + """ + event = { + "type": "resource_deleted", + "data": resource_info, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(resource_info["uri"], event) + + async def _notify_resource_removed(self, resource: DbResource) -> None: + """ + Notify subscribers of resource removal. + + Args: + resource: Resource to remove + """ + event = { + "type": "resource_removed", + "data": { + "id": resource.id, + "uri": resource.uri, + "name": resource.name, + "is_active": False, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(resource.uri, event) + + async def subscribe_events(self, uri: Optional[str] = None) -> AsyncGenerator[Dict[str, Any], None]: + """Subscribe to resource events. + + Args: + uri: Optional URI to filter events + + Yields: + Resource event messages + """ + queue: asyncio.Queue = asyncio.Queue() + + if uri: + if uri not in self._event_subscribers: + self._event_subscribers[uri] = [] + self._event_subscribers[uri].append(queue) + else: + self._event_subscribers["*"] = self._event_subscribers.get("*", []) + self._event_subscribers["*"].append(queue) + + try: + while True: + event = await queue.get() + yield event + finally: + if uri: + self._event_subscribers[uri].remove(queue) + if not self._event_subscribers[uri]: + del self._event_subscribers[uri] + else: + self._event_subscribers["*"].remove(queue) + if not self._event_subscribers["*"]: + del self._event_subscribers["*"] + + def _is_valid_uri(self, uri: str) -> bool: + """Validate a resource URI. + + Args: + uri: URI to validate + + Returns: + True if URI is valid + """ + try: + parsed = urlparse(uri) + return bool(parsed.scheme and parsed.path) + except Exception: + return False + + def _detect_mime_type(self, uri: str, content: Union[str, bytes]) -> str: + """Detect mime type from URI and content. + + Args: + uri: Resource URI + content: Resource content + + Returns: + Detected mime type + """ + # Try from URI first + mime_type, _ = mimetypes.guess_type(uri) + if mime_type: + return mime_type + + # Check content type + if isinstance(content, str): + return "text/plain" + + return "application/octet-stream" + + async def _read_template_resource(self, uri: str) -> ResourceContent: + """Read a templated resource. + + Args: + uri: Template URI with parameters + + Returns: + Resource content + + Raises: + ResourceNotFoundError: If template not found + ResourceError: For other template errors + NotImplementedError: When binary template is passed + """ + # Find matching template + template = None + for cached in self._template_cache.values(): + if self._uri_matches_template(uri, cached.uri_template): + template = cached + break + + if not template: + raise ResourceNotFoundError(f"No template matches URI: {uri}") + + try: + # Extract parameters + params = self._extract_template_params(uri, template.uri_template) + + # Generate content + if template.mime_type and template.mime_type.startswith("text/"): + content = template.uri_template.format(**params) + return TextContent(type="text", text=content) + + # Handle binary template + raise NotImplementedError("Binary resource templates not yet supported") + + except Exception as e: + raise ResourceError(f"Failed to process template: {str(e)}") + + def _uri_matches_template(self, uri: str, template: str) -> bool: + """Check if URI matches a template pattern. + + Args: + uri: URI to check + template: Template pattern + + Returns: + True if URI matches template + """ + # Convert template to regex pattern + + pattern = re.escape(template).replace(r"\{.*?\}", r"[^/]+") + return bool(re.match(pattern, uri)) + + def _extract_template_params(self, uri: str, template: str) -> Dict[str, str]: + """Extract parameters from URI based on template. + + Args: + uri: URI with parameter values + template: Template pattern + + Returns: + Dict of parameter names and values + """ + + result = parse.parse(template, uri) + return result.named if result else {} + + async def _notify_resource_added(self, resource: DbResource) -> None: + """ + Notify subscribers of resource addition. + + Args: + resource: Resource to add + """ + event = { + "type": "resource_added", + "data": { + "id": resource.id, + "uri": resource.uri, + "name": resource.name, + "description": resource.description, + "is_active": resource.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(resource.uri, event) + + async def _notify_resource_updated(self, resource: DbResource) -> None: + """ + Notify subscribers of resource update. + + Args: + resource: Resource to update + """ + event = { + "type": "resource_updated", + "data": { + "id": resource.id, + "uri": resource.uri, + "content": resource.content, + "is_active": resource.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(resource.uri, event) + + async def _publish_event(self, uri: str, event: Dict[str, Any]) -> None: + """Publish event to relevant subscribers. + + Args: + uri: Resource URI event relates to + event: Event data to publish + """ + # Notify resource-specific subscribers + if uri in self._event_subscribers: + for queue in self._event_subscribers[uri]: + await queue.put(event) + + # Notify global subscribers + if "*" in self._event_subscribers: + for queue in self._event_subscribers["*"]: + await queue.put(event) + + # --- Resource templates --- + async def list_resource_templates(self, db: Session, include_inactive: bool = False) -> List[ResourceTemplate]: + """ + Retrieve a list of resource templates from the database. + + This method retrieves resource templates (resources with a defined template field) from the database + and converts them into a list of ResourceTemplate objects. It supports filtering out inactive templates + based on the include_inactive parameter. The cursor parameter is reserved for future pagination support + but is currently not implemented. + + Args: + db (Session): The SQLAlchemy database session. + include_inactive (bool): If True, include inactive resource templates in the result. + Defaults to False. + + Returns: + List[ResourceTemplate]: A list of resource templates. + """ + query = select(DbResource).where(DbResource.template.isnot(None)) + if not include_inactive: + query = query.where(DbResource.is_active) + # Cursor-based pagination logic can be implemented here in the future. + templates = db.execute(query).scalars().all() + return [ResourceTemplate.model_validate(t) for t in templates] + + # --- Metrics --- + async def aggregate_metrics(self, db: Session) -> ResourceMetrics: + """ + Aggregate metrics for all resource invocations across all resources. + + Args: + db: Database session + + Returns: + ResourceMetrics: Aggregated metrics computed from all ResourceMetric records. + """ + total_executions = db.execute(select(func.count()).select_from(ResourceMetric)).scalar() or 0 # pylint: disable=not-callable + + successful_executions = db.execute(select(func.count()).select_from(ResourceMetric).where(ResourceMetric.is_success)).scalar() or 0 # pylint: disable=not-callable + + failed_executions = db.execute(select(func.count()).select_from(ResourceMetric).where(not_(ResourceMetric.is_success))).scalar() or 0 # pylint: disable=not-callable + + min_response_time = db.execute(select(func.min(ResourceMetric.response_time))).scalar() + + max_response_time = db.execute(select(func.max(ResourceMetric.response_time))).scalar() + + avg_response_time = db.execute(select(func.avg(ResourceMetric.response_time))).scalar() + + last_execution_time = db.execute(select(func.max(ResourceMetric.timestamp))).scalar() + + return ResourceMetrics( + total_executions=total_executions, + successful_executions=successful_executions, + failed_executions=failed_executions, + failure_rate=(failed_executions / total_executions) if total_executions > 0 else 0.0, + min_response_time=min_response_time, + max_response_time=max_response_time, + avg_response_time=avg_response_time, + last_execution_time=last_execution_time, + ) + + async def reset_metrics(self, db: Session) -> None: + """ + Reset all resource metrics by deleting all records from the resource metrics table. + + Args: + db: Database session + """ + db.execute(delete(ResourceMetric)) + db.commit() diff --git a/mcpgateway/services/root_service.py b/mcpgateway/services/root_service.py new file mode 100644 index 0000000000..0d33875a15 --- /dev/null +++ b/mcpgateway/services/root_service.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +"""Root Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements root directory management according to the MCP specification. +It handles root registration, validation, and change notifications. +""" + +import asyncio +import logging +import os +from typing import AsyncGenerator, Dict, List, Optional +from urllib.parse import urlparse + +from mcpgateway.config import settings +from mcpgateway.types import Root + +logger = logging.getLogger(__name__) + + +class RootServiceError(Exception): + """Base class for root service errors.""" + + +class RootService: + """MCP root service. + + Manages roots that can be exposed to MCP clients. + Handles: + - Root registration and validation + - Change notifications + - Root permissions and access control + """ + + def __init__(self): + """Initialize root service.""" + self._roots: Dict[str, Root] = {} + self._subscribers: List[asyncio.Queue] = [] + + async def initialize(self) -> None: + """Initialize root service.""" + logger.info("Initializing root service") + # Add any configured default roots + for root_uri in settings.default_roots: + try: + await self.add_root(root_uri) + except RootServiceError as e: + logger.error(f"Failed to add default root {root_uri}: {e}") + + async def shutdown(self) -> None: + """Shutdown root service.""" + logger.info("Shutting down root service") + # Clear all roots and subscribers + self._roots.clear() + self._subscribers.clear() + + async def list_roots(self) -> List[Root]: + """List available roots. + + Returns: + List of registered roots + """ + return list(self._roots.values()) + + async def add_root(self, uri: str, name: Optional[str] = None) -> Root: + """Add a new root. + + Args: + uri: Root URI + name: Optional root name + + Returns: + Created root object + + Raises: + RootServiceError: If root is invalid or already exists + """ + try: + root_uri = self._make_root_uri(uri) + except ValueError as e: + raise RootServiceError(f"Invalid root URI: {e}") + + if root_uri in self._roots: + raise RootServiceError(f"Root already exists: {root_uri}") + + # Skip any access check; just store the key/value. + root_obj = Root( + uri=root_uri, + name=name or os.path.basename(urlparse(root_uri).path) or root_uri, + ) + self._roots[root_uri] = root_obj + + await self._notify_root_added(root_obj) + logger.info(f"Added root: {root_uri}") + return root_obj + + async def remove_root(self, root_uri: str) -> None: + """Remove a registered root. + + Args: + root_uri: Root URI to remove + + Raises: + RootServiceError: If root not found + """ + if root_uri not in self._roots: + raise RootServiceError(f"Root not found: {root_uri}") + root_obj = self._roots.pop(root_uri) + await self._notify_root_removed(root_obj) + logger.info(f"Removed root: {root_uri}") + + async def subscribe_changes(self) -> AsyncGenerator[Dict, None]: + """Subscribe to root changes. + + Yields: + Root change events + """ + queue: asyncio.Queue = asyncio.Queue() + self._subscribers.append(queue) + try: + while True: + event = await queue.get() + yield event + finally: + self._subscribers.remove(queue) + + def _make_root_uri(self, uri: str) -> str: + """Convert input to a valid URI. + + If no scheme is provided, assume a file URI and convert the path to an absolute path. + + Args: + uri: Input URI or filesystem path + + Returns: + A valid URI string + """ + parsed = urlparse(uri) + if not parsed.scheme: + # No scheme provided; assume a file URI. + return f"file://{uri}" + # If a scheme is present (e.g., http, https, ftp, etc.), return the URI as-is. + return uri + + async def _notify_root_added(self, root: Root) -> None: + """Notify subscribers of root addition. + + Args: + root: Added root + """ + event = {"type": "root_added", "data": {"uri": root.uri, "name": root.name}} + await self._notify_subscribers(event) + + async def _notify_root_removed(self, root: Root) -> None: + """Notify subscribers of root removal. + + Args: + root: Removed root + """ + event = {"type": "root_removed", "data": {"uri": root.uri}} + await self._notify_subscribers(event) + + async def _notify_subscribers(self, event: Dict) -> None: + """Send event to all subscribers. + + Args: + event: Event to send + """ + for queue in self._subscribers: + try: + await queue.put(event) + except Exception as e: + logger.error(f"Failed to notify subscriber: {e}") diff --git a/mcpgateway/services/server_service.py b/mcpgateway/services/server_service.py new file mode 100644 index 0000000000..78a5e4f7e2 --- /dev/null +++ b/mcpgateway/services/server_service.py @@ -0,0 +1,624 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +MCP Gateway Server Service + +This module implements server management for the MCP Servers Catalog. +It handles server registration, listing, retrieval, updates, activation toggling, and deletion. +It also publishes event notifications for server changes. +""" + +import asyncio +import logging +from datetime import datetime +from typing import Any, AsyncGenerator, Dict, List, Optional + +import httpx +from sqlalchemy import delete, func, not_, select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session + +from mcpgateway.config import settings +from mcpgateway.db import Prompt as DbPrompt +from mcpgateway.db import Resource as DbResource +from mcpgateway.db import Server as DbServer +from mcpgateway.db import ServerMetric +from mcpgateway.db import Tool as DbTool +from mcpgateway.schemas import ServerCreate, ServerMetrics, ServerRead, ServerUpdate + +logger = logging.getLogger(__name__) + + +class ServerError(Exception): + """Base class for server-related errors.""" + + +class ServerNotFoundError(ServerError): + """Raised when a requested server is not found.""" + + +class ServerNameConflictError(ServerError): + """Raised when a server name conflicts with an existing one.""" + + def __init__(self, name: str, is_active: bool = True, server_id: Optional[int] = None): + self.name = name + self.is_active = is_active + self.server_id = server_id + message = f"Server already exists with name: {name}" + if not is_active: + message += f" (currently inactive, ID: {server_id})" + super().__init__(message) + + +class ServerService: + """Service for managing MCP Servers in the catalog. + + Provides methods to create, list, retrieve, update, toggle status, and delete server records. + Also supports event notifications for changes in server data. + """ + + def __init__(self) -> None: + self._event_subscribers: List[asyncio.Queue] = [] + self._http_client = httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) + + async def initialize(self) -> None: + """Initialize the server service.""" + logger.info("Initializing server service") + + async def shutdown(self) -> None: + """Shutdown the server service.""" + await self._http_client.aclose() + logger.info("Server service shutdown complete") + + def _convert_server_to_read(self, server: DbServer) -> ServerRead: + """ + Converts a DbServer instance into a ServerRead model, including aggregated metrics. + + Args: + server (DbServer): The ORM instance of the server. + + Returns: + ServerRead: The Pydantic model representing the server, including aggregated metrics. + """ + server_dict = server.__dict__.copy() + server_dict.pop("_sa_instance_state", None) + # Compute aggregated metrics from server.metrics; default to 0/None when no records exist. + total = len(server.metrics) if hasattr(server, "metrics") else 0 + successful = sum(1 for m in server.metrics if m.is_success) if total > 0 else 0 + failed = sum(1 for m in server.metrics if not m.is_success) if total > 0 else 0 + failure_rate = (failed / total) if total > 0 else 0.0 + min_rt = min((m.response_time for m in server.metrics), default=None) if total > 0 else None + max_rt = max((m.response_time for m in server.metrics), default=None) if total > 0 else None + avg_rt = (sum(m.response_time for m in server.metrics) / total) if total > 0 else None + last_time = max((m.timestamp for m in server.metrics), default=None) if total > 0 else None + + server_dict["metrics"] = { + "total_executions": total, + "successful_executions": successful, + "failed_executions": failed, + "failure_rate": failure_rate, + "min_response_time": min_rt, + "max_response_time": max_rt, + "avg_response_time": avg_rt, + "last_execution_time": last_time, + } + # Also update associated IDs (if not already done) + server_dict["associated_tools"] = [tool.id for tool in server.tools] if server.tools else [] + server_dict["associated_resources"] = [res.id for res in server.resources] if server.resources else [] + server_dict["associated_prompts"] = [prompt.id for prompt in server.prompts] if server.prompts else [] + return ServerRead.model_validate(server_dict) + + def _assemble_associated_items( + self, + tools: Optional[List[str]], + resources: Optional[List[str]], + prompts: Optional[List[str]], + ) -> Dict[str, Any]: + """ + Assemble the associated items dictionary from the separate fields. + + Args: + tools: List of tool IDs. + resources: List of resource IDs. + prompts: List of prompt IDs. + + Returns: + A dictionary with keys "tools", "resources", and "prompts". + """ + return { + "tools": tools or [], + "resources": resources or [], + "prompts": prompts or [], + } + + async def register_server(self, db: Session, server_in: ServerCreate) -> ServerRead: + """ + Register a new server in the catalog and validate that all associated items exist. + + This function performs the following steps: + 1. Checks if a server with the same name already exists. + 2. Creates a new server record. + 3. For each ID provided in associated_tools, associated_resources, and associated_prompts, + verifies that the corresponding item exists. If an item does not exist, an error is raised. + 4. Associates the verified items to the new server. + 5. Commits the transaction, refreshes the ORM instance, and forces the loading of relationship data. + 6. Constructs a response dictionary that includes lists of associated item IDs. + 7. Notifies subscribers of the addition and returns the validated response. + + Args: + db (Session): The SQLAlchemy database session. + server_in (ServerCreate): The server creation schema containing server details and lists of + associated tool, resource, and prompt IDs (as strings). + + Returns: + ServerRead: The newly created server, with associated item IDs. + + Raises: + ServerNameConflictError: If a server with the same name already exists. + ServerError: If any associated tool, resource, or prompt does not exist, or if any other + registration error occurs. + """ + try: + # Check for an existing server with the same name. + existing = db.execute(select(DbServer).where(DbServer.name == server_in.name)).scalar_one_or_none() + if existing: + raise ServerNameConflictError(server_in.name, is_active=existing.is_active, server_id=existing.id) + + # Create the new server record. + db_server = DbServer( + name=server_in.name, + description=server_in.description, + icon=server_in.icon, + is_active=True, + ) + db.add(db_server) + + # Associate tools, verifying each exists. + if server_in.associated_tools: + for tool_id in server_in.associated_tools: + if tool_id.strip() == "": + continue + tool_obj = db.get(DbTool, int(tool_id)) + if not tool_obj: + raise ServerError(f"Tool with id {tool_id} does not exist.") + db_server.tools.append(tool_obj) + + # Associate resources, verifying each exists. + if server_in.associated_resources: + for resource_id in server_in.associated_resources: + if resource_id.strip() == "": + continue + resource_obj = db.get(DbResource, int(resource_id)) + if not resource_obj: + raise ServerError(f"Resource with id {resource_id} does not exist.") + db_server.resources.append(resource_obj) + + # Associate prompts, verifying each exists. + if server_in.associated_prompts: + for prompt_id in server_in.associated_prompts: + if prompt_id.strip() == "": + continue + prompt_obj = db.get(DbPrompt, int(prompt_id)) + if not prompt_obj: + raise ServerError(f"Prompt with id {prompt_id} does not exist.") + db_server.prompts.append(prompt_obj) + + # Commit the new record and refresh. + db.commit() + db.refresh(db_server) + # Force load the relationship attributes. + _ = db_server.tools, db_server.resources, db_server.prompts + + # Assemble response data with associated item IDs. + server_data = { + "id": db_server.id, + "name": db_server.name, + "description": db_server.description, + "icon": db_server.icon, + "created_at": db_server.created_at, + "updated_at": db_server.updated_at, + "is_active": db_server.is_active, + "associated_tools": [str(tool.id) for tool in db_server.tools], + "associated_resources": [str(resource.id) for resource in db_server.resources], + "associated_prompts": [str(prompt.id) for prompt in db_server.prompts], + } + logger.debug(f"Server Data: {server_data}") + await self._notify_server_added(db_server) + logger.info(f"Registered server: {server_in.name}") + return self._convert_server_to_read(db_server) + except IntegrityError: + db.rollback() + raise ServerError(f"Server already exists: {server_in.name}") + except Exception as e: + db.rollback() + raise ServerError(f"Failed to register server: {str(e)}") + + async def list_servers(self, db: Session, include_inactive: bool = False) -> List[ServerRead]: + """List all registered servers. + + Args: + db: Database session. + include_inactive: Whether to include inactive servers. + + Returns: + A list of ServerRead objects. + """ + query = select(DbServer) + if not include_inactive: + query = query.where(DbServer.is_active) + servers = db.execute(query).scalars().all() + return [self._convert_server_to_read(s) for s in servers] + + async def get_server(self, db: Session, server_id: int) -> ServerRead: + """Retrieve server details by ID. + + Args: + db: Database session. + server_id: The unique identifier of the server. + + Returns: + The corresponding ServerRead object. + + Raises: + ServerNotFoundError: If no server with the given ID exists. + """ + server = db.get(DbServer, server_id) + if not server: + raise ServerNotFoundError(f"Server not found: {server_id}") + server_data = { + "id": server.id, + "name": server.name, + "description": server.description, + "icon": server.icon, + "created_at": server.created_at, + "updated_at": server.updated_at, + "is_active": server.is_active, + "associated_tools": [tool.id for tool in server.tools], + "associated_resources": [res.id for res in server.resources], + "associated_prompts": [prompt.id for prompt in server.prompts], + } + logger.debug(f"Server Data: {server_data}") + return self._convert_server_to_read(server) + + async def update_server(self, db: Session, server_id: int, server_update: ServerUpdate) -> ServerRead: + """Update an existing server. + + Args: + db: Database session. + server_id: The unique identifier of the server. + server_update: Server update schema with new data. + + Returns: + The updated ServerRead object. + + Raises: + ServerNotFoundError: If the server is not found. + ServerNameConflictError: If a new name conflicts with an existing server. + ServerError: For other update errors. + """ + try: + server = db.get(DbServer, server_id) + if not server: + raise ServerNotFoundError(f"Server not found: {server_id}") + + # Check for name conflict if name is being changed + if server_update.name and server_update.name != server.name: + conflict = db.execute(select(DbServer).where(DbServer.name == server_update.name).where(DbServer.id != server_id)).scalar_one_or_none() + if conflict: + raise ServerNameConflictError( + server_update.name, + is_active=conflict.is_active, + server_id=conflict.id, + ) + + # Update simple fields + if server_update.name is not None: + server.name = server_update.name + if server_update.description is not None: + server.description = server_update.description + if server_update.icon is not None: + server.icon = server_update.icon + + # Update associated tools if provided + if server_update.associated_tools is not None: + server.tools = [] + for tool_id in server_update.associated_tools: + tool_obj = db.get(DbTool, int(tool_id)) + if tool_obj: + server.tools.append(tool_obj) + + # Update associated resources if provided + if server_update.associated_resources is not None: + server.resources = [] + for resource_id in server_update.associated_resources: + resource_obj = db.get(DbResource, int(resource_id)) + if resource_obj: + server.resources.append(resource_obj) + + # Update associated prompts if provided + if server_update.associated_prompts is not None: + server.prompts = [] + for prompt_id in server_update.associated_prompts: + prompt_obj = db.get(DbPrompt, int(prompt_id)) + if prompt_obj: + server.prompts.append(prompt_obj) + + server.updated_at = datetime.utcnow() + db.commit() + db.refresh(server) + # Force loading relationships + _ = server.tools, server.resources, server.prompts + + await self._notify_server_updated(server) + logger.info(f"Updated server: {server.name}") + + # Build a dictionary with associated IDs + server_data = { + "id": server.id, + "name": server.name, + "description": server.description, + "icon": server.icon, + "created_at": server.created_at, + "updated_at": server.updated_at, + "is_active": server.is_active, + "associated_tools": [tool.id for tool in server.tools], + "associated_resources": [res.id for res in server.resources], + "associated_prompts": [prompt.id for prompt in server.prompts], + } + logger.debug(f"Server Data: {server_data}") + return self._convert_server_to_read(server) + except Exception as e: + db.rollback() + raise ServerError(f"Failed to update server: {str(e)}") + + async def toggle_server_status(self, db: Session, server_id: int, activate: bool) -> ServerRead: + """Toggle the activation status of a server. + + Args: + db: Database session. + server_id: The unique identifier of the server. + activate: True to activate, False to deactivate. + + Returns: + The updated ServerRead object. + + Raises: + ServerNotFoundError: If the server is not found. + ServerError: For other errors. + """ + try: + server = db.get(DbServer, server_id) + if not server: + raise ServerNotFoundError(f"Server not found: {server_id}") + + if server.is_active != activate: + server.is_active = activate + server.updated_at = datetime.utcnow() + db.commit() + db.refresh(server) + if activate: + await self._notify_server_activated(server) + else: + await self._notify_server_deactivated(server) + logger.info(f"Server {server.name} {'activated' if activate else 'deactivated'}") + + server_data = { + "id": server.id, + "name": server.name, + "description": server.description, + "icon": server.icon, + "created_at": server.created_at, + "updated_at": server.updated_at, + "is_active": server.is_active, + "associated_tools": [tool.id for tool in server.tools], + "associated_resources": [res.id for res in server.resources], + "associated_prompts": [prompt.id for prompt in server.prompts], + } + logger.debug(f"Server Data: {server_data}") + return self._convert_server_to_read(server) + except Exception as e: + db.rollback() + raise ServerError(f"Failed to toggle server status: {str(e)}") + + async def delete_server(self, db: Session, server_id: int) -> None: + """Permanently delete a server. + + Args: + db: Database session. + server_id: The unique identifier of the server. + + Raises: + ServerNotFoundError: If the server is not found. + ServerError: For other deletion errors. + """ + try: + server = db.get(DbServer, server_id) + if not server: + raise ServerNotFoundError(f"Server not found: {server_id}") + + server_info = {"id": server.id, "name": server.name} + db.delete(server) + db.commit() + + await self._notify_server_deleted(server_info) + logger.info(f"Deleted server: {server_info['name']}") + except Exception as e: + db.rollback() + raise ServerError(f"Failed to delete server: {str(e)}") + + async def _publish_event(self, event: Dict[str, Any]) -> None: + """ + Publish an event to all subscribed queues. + + Args: + event: Event to publish + """ + for queue in self._event_subscribers: + await queue.put(event) + + async def subscribe_events(self) -> AsyncGenerator[Dict[str, Any], None]: + """Subscribe to server events. + + Yields: + Server event messages. + """ + queue: asyncio.Queue = asyncio.Queue() + self._event_subscribers.append(queue) + try: + while True: + event = await queue.get() + yield event + finally: + self._event_subscribers.remove(queue) + + async def _notify_server_added(self, server: DbServer) -> None: + """ + Notify subscribers that a new server has been added. + + Args: + server: Server to add + """ + associated_tools = [tool.id for tool in server.tools] if server.tools else [] + associated_resources = [res.id for res in server.resources] if server.resources else [] + associated_prompts = [prompt.id for prompt in server.prompts] if server.prompts else [] + event = { + "type": "server_added", + "data": { + "id": server.id, + "name": server.name, + "description": server.description, + "icon": server.icon, + "associated_tools": associated_tools, + "associated_resources": associated_resources, + "associated_prompts": associated_prompts, + "is_active": server.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_server_updated(self, server: DbServer) -> None: + """ + Notify subscribers that a server has been updated. + + Args: + server: Server to update + """ + associated_tools = [tool.id for tool in server.tools] if server.tools else [] + associated_resources = [res.id for res in server.resources] if server.resources else [] + associated_prompts = [prompt.id for prompt in server.prompts] if server.prompts else [] + event = { + "type": "server_updated", + "data": { + "id": server.id, + "name": server.name, + "description": server.description, + "icon": server.icon, + "associated_tools": associated_tools, + "associated_resources": associated_resources, + "associated_prompts": associated_prompts, + "is_active": server.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_server_activated(self, server: DbServer) -> None: + """ + Notify subscribers that a server has been activated. + + Args: + server: Server to activate + """ + event = { + "type": "server_activated", + "data": { + "id": server.id, + "name": server.name, + "is_active": True, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_server_deactivated(self, server: DbServer) -> None: + """ + Notify subscribers that a server has been deactivated. + + Args: + server: Server to deactivate + """ + event = { + "type": "server_deactivated", + "data": { + "id": server.id, + "name": server.name, + "is_active": False, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_server_deleted(self, server_info: Dict[str, Any]) -> None: + """ + Notify subscribers that a server has been deleted. + + Args: + server_info: Dictionary on server to be deleted + """ + event = { + "type": "server_deleted", + "data": server_info, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + # --- Metrics --- + async def aggregate_metrics(self, db: Session) -> ServerMetrics: + """ + Aggregate metrics for all server invocations across all servers. + + Args: + db: Database session + + Returns: + ServerMetrics: Aggregated metrics computed from all ServerMetric records. + """ + total_executions = db.execute(select(func.count()).select_from(ServerMetric)).scalar() or 0 # pylint: disable=not-callable + + successful_executions = db.execute(select(func.count()).select_from(ServerMetric).where(ServerMetric.is_success)).scalar() or 0 # pylint: disable=not-callable + + failed_executions = db.execute(select(func.count()).select_from(ServerMetric).where(not_(ServerMetric.is_success))).scalar() or 0 # pylint: disable=not-callable + + min_response_time = db.execute(select(func.min(ServerMetric.response_time))).scalar() + + max_response_time = db.execute(select(func.max(ServerMetric.response_time))).scalar() + + avg_response_time = db.execute(select(func.avg(ServerMetric.response_time))).scalar() + + last_execution_time = db.execute(select(func.max(ServerMetric.timestamp))).scalar() + + return ServerMetrics( + total_executions=total_executions, + successful_executions=successful_executions, + failed_executions=failed_executions, + failure_rate=(failed_executions / total_executions) if total_executions > 0 else 0.0, + min_response_time=min_response_time, + max_response_time=max_response_time, + avg_response_time=avg_response_time, + last_execution_time=last_execution_time, + ) + + async def reset_metrics(self, db: Session) -> None: + """ + Reset all server metrics by deleting all records from the server metrics table. + + Args: + db: Database session + """ + db.execute(delete(ServerMetric)) + db.commit() diff --git a/mcpgateway/services/tool_service.py b/mcpgateway/services/tool_service.py new file mode 100644 index 0000000000..b3100c6581 --- /dev/null +++ b/mcpgateway/services/tool_service.py @@ -0,0 +1,838 @@ +# -*- coding: utf-8 -*- +"""Tool Service Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements tool management and invocation according to the MCP specification. +It handles: +- Tool registration and validation +- Tool invocation with schema validation +- Tool federation across gateways +- Event notifications for tool changes +- Active/inactive tool management +""" + +import asyncio +import json +import logging +import time +from datetime import datetime +from typing import Any, AsyncGenerator, Dict, List, Optional + +import httpx +from mcp import ClientSession +from mcp.client.sse import sse_client +from sqlalchemy import delete, func, not_, select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session + +from mcpgateway.config import settings +from mcpgateway.db import Gateway as DbGateway +from mcpgateway.db import Tool as DbTool +from mcpgateway.db import ToolMetric, server_tool_association +from mcpgateway.schemas import ( + ToolCreate, + ToolRead, + ToolUpdate, +) +from mcpgateway.types import TextContent, ToolResult +from mcpgateway.utils.services_auth import decode_auth + +from ..config import extract_using_jq + +logger = logging.getLogger(__name__) + + +class ToolError(Exception): + """Base class for tool-related errors.""" + + +class ToolNotFoundError(ToolError): + """Raised when a requested tool is not found.""" + + +class ToolNameConflictError(ToolError): + """Raised when a tool name conflicts with existing (active or inactive) tool.""" + + def __init__(self, name: str, is_active: bool = True, tool_id: Optional[int] = None): + """Initialize the error with tool information. + + Args: + name: The conflicting tool name. + is_active: Whether the existing tool is active. + tool_id: ID of the existing tool if available. + """ + self.name = name + self.is_active = is_active + self.tool_id = tool_id + message = f"Tool already exists with name: {name}" + if not is_active: + message += f" (currently inactive, ID: {tool_id})" + super().__init__(message) + + +class ToolValidationError(ToolError): + """Raised when tool validation fails.""" + + +class ToolInvocationError(ToolError): + """Raised when tool invocation fails.""" + + +class ToolService: + """Service for managing and invoking tools. + + Handles: + - Tool registration and deregistration. + - Tool invocation and validation. + - Tool federation. + - Event notifications. + - Active/inactive tool management. + """ + + def __init__(self): + """Initialize the tool service.""" + self._event_subscribers: List[asyncio.Queue] = [] + self._http_client = httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) + + async def initialize(self) -> None: + """Initialize the service.""" + logger.info("Initializing tool service") + + async def shutdown(self) -> None: + """Shutdown the service.""" + await self._http_client.aclose() + logger.info("Tool service shutdown complete") + + def _convert_tool_to_read(self, tool: DbTool) -> ToolRead: + """ + Converts a DbTool instance into a ToolRead model, including aggregated metrics and + new API gateway fields: request_type and authentication credentials (masked). + + Args: + tool (DbTool): The ORM instance of the tool. + + Returns: + ToolRead: The Pydantic model representing the tool, including aggregated metrics and new fields. + """ + tool_dict = tool.__dict__.copy() + tool_dict.pop("_sa_instance_state", None) + tool_dict["execution_count"] = tool.execution_count + tool_dict["metrics"] = tool.metrics_summary + tool_dict["request_type"] = tool.request_type + + decoded_auth_value = decode_auth(tool.auth_value) + if tool.auth_type == "basic": + tool_dict["auth"] = { + "auth_type": "basic", + "username": decoded_auth_value["username"], + "password": "********" if decoded_auth_value["password"] else None, + } + elif tool.auth_type == "bearer": + tool_dict["auth"] = { + "auth_type": "bearer", + "token": "********" if decoded_auth_value["Authorization"] else None, + } + elif tool.auth_type == "authheaders": + tool_dict["auth"] = { + "auth_type": "authheaders", + "auth_header_key": next(iter(decoded_auth_value)), + "auth_header_value": "********" if decoded_auth_value[next(iter(decoded_auth_value))] else None, + } + else: + tool_dict["auth"] = None + return ToolRead.model_validate(tool_dict) + + async def _record_tool_metric(self, db: Session, tool: DbTool, start_time: float, success: bool, error_message: Optional[str]) -> None: + """ + Records a metric for a tool invocation. + + This function calculates the response time using the provided start time and records + the metric details (including whether the invocation was successful and any error message) + into the database. The metric is then committed to the database. + + Args: + db (Session): The SQLAlchemy database session. + tool (DbTool): The tool that was invoked. + start_time (float): The monotonic start time of the invocation. + success (bool): True if the invocation succeeded; otherwise, False. + error_message (Optional[str]): The error message if the invocation failed, otherwise None. + """ + end_time = time.monotonic() + response_time = end_time - start_time + metric = ToolMetric( + tool_id=tool.id, + response_time=response_time, + is_success=success, + error_message=error_message, + ) + db.add(metric) + db.commit() + + async def register_tool(self, db: Session, tool: ToolCreate) -> ToolRead: + """Register a new tool. + + Args: + db: Database session. + tool: Tool creation schema. + + Returns: + Created tool information. + + Raises: + ToolNameConflictError: If tool name already exists. + ToolError: For other tool registration errors. + """ + try: + existing_tool = db.execute(select(DbTool).where(DbTool.name == tool.name)).scalar_one_or_none() + if existing_tool: + raise ToolNameConflictError( + tool.name, + is_active=existing_tool.is_active, + tool_id=existing_tool.id, + ) + + if tool.auth is None: + auth_type = None + auth_value = None + else: + auth_type = tool.auth.auth_type + auth_value = tool.auth.auth_value + + db_tool = DbTool( + name=tool.name, + url=str(tool.url), + description=tool.description, + integration_type=tool.integration_type, + request_type=tool.request_type, + headers=tool.headers, + input_schema=tool.input_schema, + jsonpath_filter=tool.jsonpath_filter, + auth_type=auth_type, + auth_value=auth_value, + gateway_id=tool.gateway_id, + ) + db.add(db_tool) + db.commit() + db.refresh(db_tool) + await self._notify_tool_added(db_tool) + logger.info(f"Registered tool: {tool.name}") + return self._convert_tool_to_read(db_tool) + except IntegrityError: + db.rollback() + raise ToolError(f"Tool already exists: {tool.name}") + except Exception as e: + db.rollback() + raise ToolError(f"Failed to register tool: {str(e)}") + + async def list_tools(self, db: Session, include_inactive: bool = False, cursor: Optional[str] = None) -> List[ToolRead]: + """ + Retrieve a list of registered tools from the database. + + Args: + db (Session): The SQLAlchemy database session. + include_inactive (bool): If True, include inactive tools in the result. + Defaults to False. + cursor (Optional[str], optional): An opaque cursor token for pagination. Currently, + this parameter is ignored. Defaults to None. + + Returns: + List[ToolRead]: A list of registered tools represented as ToolRead objects. + """ + query = select(DbTool) + cursor = None # Placeholder for pagination; ignore for now + logger.debug(f"Listing tools with include_inactive={include_inactive}, cursor={cursor}") + if not include_inactive: + query = query.where(DbTool.is_active) + tools = db.execute(query).scalars().all() + return [self._convert_tool_to_read(t) for t in tools] + + async def list_server_tools(self, db: Session, server_id: int, include_inactive: bool = False, cursor: Optional[str] = None) -> List[ToolRead]: + """ + Retrieve a list of registered tools from the database. + + Args: + db (Session): The SQLAlchemy database session. + server_id (int): Server ID + include_inactive (bool): If True, include inactive tools in the result. + Defaults to False. + cursor (Optional[str], optional): An opaque cursor token for pagination. Currently, + this parameter is ignored. Defaults to None. + + Returns: + List[ToolRead]: A list of registered tools represented as ToolRead objects. + """ + query = select(DbTool).join(server_tool_association, DbTool.id == server_tool_association.c.tool_id).where(server_tool_association.c.server_id == server_id) + cursor = None # Placeholder for pagination; ignore for now + logger.debug(f"Listing server tools for server_id={server_id} with include_inactive={include_inactive}, cursor={cursor}") + if not include_inactive: + query = query.where(DbTool.is_active) + tools = db.execute(query).scalars().all() + return [self._convert_tool_to_read(t) for t in tools] + + async def get_tool(self, db: Session, tool_id: int) -> ToolRead: + """Get a specific tool by ID. + + Args: + db: Database session. + tool_id: Tool ID to retrieve. + + Returns: + Tool information. + + Raises: + ToolNotFoundError: If tool not found. + """ + tool = db.get(DbTool, tool_id) + if not tool: + raise ToolNotFoundError(f"Tool not found: {tool_id}") + return self._convert_tool_to_read(tool) + + async def delete_tool(self, db: Session, tool_id: int) -> None: + """Permanently delete a tool from the database. + + Args: + db: Database session. + tool_id: Tool ID to delete. + + Raises: + ToolNotFoundError: If tool not found. + ToolError: For other deletion errors. + """ + try: + tool = db.get(DbTool, tool_id) + if not tool: + raise ToolNotFoundError(f"Tool not found: {tool_id}") + tool_info = {"id": tool.id, "name": tool.name} + db.delete(tool) + db.commit() + await self._notify_tool_deleted(tool_info) + logger.info(f"Permanently deleted tool: {tool_info['name']}") + except Exception as e: + db.rollback() + raise ToolError(f"Failed to delete tool: {str(e)}") + + async def toggle_tool_status(self, db: Session, tool_id: int, activate: bool) -> ToolRead: + """Toggle tool active status. + + Args: + db: Database session. + tool_id: Tool ID to toggle. + activate: True to activate, False to deactivate. + + Returns: + Updated tool information. + + Raises: + ToolNotFoundError: If tool not found. + ToolError: For other errors. + """ + try: + tool = db.get(DbTool, tool_id) + if not tool: + raise ToolNotFoundError(f"Tool not found: {tool_id}") + if tool.is_active != activate: + tool.is_active = activate + tool.updated_at = datetime.utcnow() + db.commit() + db.refresh(tool) + if activate: + await self._notify_tool_activated(tool) + else: + await self._notify_tool_deactivated(tool) + logger.info(f"Tool {tool.name} {'activated' if activate else 'deactivated'}") + return self._convert_tool_to_read(tool) + except Exception as e: + db.rollback() + raise ToolError(f"Failed to toggle tool status: {str(e)}") + + # async def invoke_tool(self, db: Session, name: str, arguments: Dict[str, Any]) -> ToolResult: + # """ + # Invoke a registered tool and record execution metrics. + + # Args: + # db: Database session. + # name: Name of tool to invoke. + # arguments: Tool arguments. + + # Returns: + # Tool invocation result. + + # Raises: + # ToolNotFoundError: If tool not found. + # ToolInvocationError: If invocation fails. + # """ + + # tool = db.execute(select(DbTool).where(DbTool.name == name).where(DbTool.is_active)).scalar_one_or_none() + # if not tool: + # inactive_tool = db.execute(select(DbTool).where(DbTool.name == name).where(not_(DbTool.is_active))).scalar_one_or_none() + # if inactive_tool: + # raise ToolNotFoundError(f"Tool '{name}' exists but is inactive") + # raise ToolNotFoundError(f"Tool not found: {name}") + # start_time = time.monotonic() + # success = False + # error_message = None + # try: + # # tool.validate_arguments(arguments) + # # Build headers with auth if necessary. + # headers = tool.headers or {} + # if tool.integration_type == "REST": + # credentials = decode_auth(tool.auth_value) + # headers.update(credentials) + + # # Build the payload based on integration type. + # payload = arguments + + # # Use the tool's request_type rather than defaulting to POST. + # method = tool.request_type.upper() + # if method == "GET": + # response = await self._http_client.get(tool.url, params=payload, headers=headers) + # else: + # response = await self._http_client.request(method, tool.url, json=payload, headers=headers) + # response.raise_for_status() + # result = response.json() + + # if response.status_code not in [200, 201, 202, 204, 206]: + # tool_result = ToolResult( + # content=[TextContent(type="text", text=str(result["error"]) if "error" in result else "Tool error encountered")], + # is_error=True, + # ) + # else: + # filtered_response = extract_using_jq(result, tool.jsonpath_filter) + # tool_result = ToolResult(content=[TextContent(type="text", text=json.dumps(filtered_response, indent=2))]) + + # success = True + # elif tool.integration_type == "MCP": + # gateway = db.execute(select(DbGateway).where(DbGateway.id == tool.gateway_id).where(DbGateway.is_active)).scalar_one_or_none() + # if gateway.auth_type == "bearer": + # headers = decode_auth(gateway.auth_value) + # else: + # headers = {} + + # async def connect_to_sse_server(server_url: str): + # """ + # Connect to an MCP server running with SSE transport + + # Args: + # server_url: Server URL + + # Returns: + # str: Tool call result + # """ + # # Store the context managers so they stay alive + # _streams_context = sse_client(url=server_url, headers=headers) + # streams = await _streams_context.__aenter__() #line 422 + + # _session_context = ClientSession(*streams) + # session: ClientSession = await _session_context.__aenter__() #line 425 + + # # Initialize + # await session.initialize() + # tool_call_result = await session.call_tool(name, arguments) + + # await _session_context.__aexit__(None, None, None) + # await _streams_context.__aexit__(None, None, None) #line 432 + + # return tool_call_result + + # tool_gateway_id = tool.gateway_id + # tool_gateway = db.execute(select(DbGateway).where(DbGateway.id == tool_gateway_id).where(DbGateway.is_active)).scalar_one_or_none() + + # tool_call_result = await connect_to_sse_server(tool_gateway.url) + # content = tool_call_result.model_dump(by_alias=True).get("content", []) + + # success = True + # filtered_response = extract_using_jq(content, tool.jsonpath_filter) + # tool_result = ToolResult(content=filtered_response) + # else: + # return ToolResult(content="Invalid tool type") + + # return tool_result + # except Exception as e: + # error_message = str(e) + # raise ToolInvocationError(f"Tool invocation failed: {error_message}") + # finally: + # await self._record_tool_metric(db, tool, start_time, success, error_message) + + async def invoke_tool(self, db: Session, name: str, arguments: Dict[str, Any]) -> ToolResult: + """ + Invoke a registered tool and record execution metrics. + + Args: + db: Database session. + name: Name of tool to invoke. + arguments: Tool arguments. + + Returns: + Tool invocation result. + + Raises: + ToolNotFoundError: If tool not found. + ToolInvocationError: If invocation fails. + """ + tool = db.execute(select(DbTool).where(DbTool.name == name).where(DbTool.is_active)).scalar_one_or_none() + if not tool: + inactive_tool = db.execute(select(DbTool).where(DbTool.name == name).where(not_(DbTool.is_active))).scalar_one_or_none() + if inactive_tool: + raise ToolNotFoundError(f"Tool '{name}' exists but is inactive") + raise ToolNotFoundError(f"Tool not found: {name}") + start_time = time.monotonic() + success = False + error_message = None + try: + # tool.validate_arguments(arguments) + # Build headers with auth if necessary. + headers = tool.headers or {} + if tool.integration_type == "REST": + credentials = decode_auth(tool.auth_value) + headers.update(credentials) + + # Build the payload based on integration type. + payload = arguments + + # Use the tool's request_type rather than defaulting to POST. + method = tool.request_type.upper() + if method == "GET": + response = await self._http_client.get(tool.url, params=payload, headers=headers) + else: + response = await self._http_client.request(method, tool.url, json=payload, headers=headers) + response.raise_for_status() + result = response.json() + + if response.status_code not in [200, 201, 202, 204, 206]: + tool_result = ToolResult( + content=[TextContent(type="text", text=str(result["error"]) if "error" in result else "Tool error encountered")], + is_error=True, + ) + else: + filtered_response = extract_using_jq(result, tool.jsonpath_filter) + tool_result = ToolResult(content=[TextContent(type="text", text=json.dumps(filtered_response, indent=2))]) + + success = True + elif tool.integration_type == "MCP": + gateway = db.execute(select(DbGateway).where(DbGateway.id == tool.gateway_id).where(DbGateway.is_active)).scalar_one_or_none() + if gateway.auth_type == "bearer": + headers = decode_auth(gateway.auth_value) + else: + headers = {} + + async def connect_to_sse_server(server_url: str): + """ + Connect to an MCP server running with SSE transport + """ + # Use async with directly to manage the context + async with sse_client(url=server_url, headers=headers) as streams: + async with ClientSession(*streams) as session: + # Initialize the session + await session.initialize() + tool_call_result = await session.call_tool(name, arguments) + return tool_call_result + + tool_gateway_id = tool.gateway_id + tool_gateway = db.execute(select(DbGateway).where(DbGateway.id == tool_gateway_id).where(DbGateway.is_active)).scalar_one_or_none() + + tool_call_result = await connect_to_sse_server(tool_gateway.url) + content = tool_call_result.model_dump(by_alias=True).get("content", []) + + success = True + filtered_response = extract_using_jq(content, tool.jsonpath_filter) + tool_result = ToolResult(content=filtered_response) + else: + return ToolResult(content="Invalid tool type") + + return tool_result + except Exception as e: + error_message = str(e) + raise ToolInvocationError(f"Tool invocation failed: {error_message}") + finally: + await self._record_tool_metric(db, tool, start_time, success, error_message) + + async def update_tool(self, db: Session, tool_id: int, tool_update: ToolUpdate) -> ToolRead: + """Update an existing tool. + + Args: + db: Database session. + tool_id: ID of tool to update. + tool_update: Updated tool data. + + Returns: + Updated tool information. + + Raises: + ToolNotFoundError: If tool not found. + ToolError: For other tool update errors. + ToolNameConflictError: If tool name conflict occurs + """ + try: + tool = db.get(DbTool, tool_id) + if not tool: + raise ToolNotFoundError(f"Tool not found: {tool_id}") + if tool_update.name is not None and tool_update.name != tool.name: + existing_tool = db.execute(select(DbTool).where(DbTool.name == tool_update.name).where(DbTool.id != tool_id)).scalar_one_or_none() + if existing_tool: + raise ToolNameConflictError( + tool_update.name, + is_active=existing_tool.is_active, + tool_id=existing_tool.id, + ) + + if tool_update.name is not None: + tool.name = tool_update.name + if tool_update.url is not None: + tool.url = str(tool_update.url) + if tool_update.description is not None: + tool.description = tool_update.description + if tool_update.integration_type is not None: + tool.integration_type = tool_update.integration_type + if tool_update.request_type is not None: + tool.request_type = tool_update.request_type + if tool_update.headers is not None: + tool.headers = tool_update.headers + if tool_update.input_schema is not None: + tool.input_schema = tool_update.input_schema + if tool_update.jsonpath_filter is not None: + tool.jsonpath_filter = tool_update.jsonpath_filter + + if tool_update.auth is not None: + if tool_update.auth.auth_type is not None: + tool.auth_type = tool_update.auth.auth_type + if tool_update.auth.auth_value is not None: + tool.auth_value = tool_update.auth.auth_value + else: + tool.auth_type = None + + tool.updated_at = datetime.utcnow() + db.commit() + db.refresh(tool) + await self._notify_tool_updated(tool) + logger.info(f"Updated tool: {tool.name}") + return self._convert_tool_to_read(tool) + except Exception as e: + db.rollback() + raise ToolError(f"Failed to update tool: {str(e)}") + + async def _notify_tool_updated(self, tool: DbTool) -> None: + """ + Notify subscribers of tool update. + + Args: + tool: Tool updated + """ + event = { + "type": "tool_updated", + "data": { + "id": tool.id, + "name": tool.name, + "url": tool.url, + "description": tool.description, + "is_active": tool.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_tool_activated(self, tool: DbTool) -> None: + """ + Notify subscribers of tool activation. + + Args: + tool: Tool activated + """ + event = { + "type": "tool_activated", + "data": {"id": tool.id, "name": tool.name, "is_active": True}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_tool_deactivated(self, tool: DbTool) -> None: + """ + Notify subscribers of tool deactivation. + + Args: + tool: Tool deactivated + """ + event = { + "type": "tool_deactivated", + "data": {"id": tool.id, "name": tool.name, "is_active": False}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_tool_deleted(self, tool_info: Dict[str, Any]) -> None: + """ + Notify subscribers of tool deletion. + + Args: + tool_info: Dictionary on tool deleted + """ + event = { + "type": "tool_deleted", + "data": tool_info, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def subscribe_events(self) -> AsyncGenerator[Dict[str, Any], None]: + """Subscribe to tool events. + + Yields: + Tool event messages. + """ + queue: asyncio.Queue = asyncio.Queue() + self._event_subscribers.append(queue) + try: + while True: + event = await queue.get() + yield event + finally: + self._event_subscribers.remove(queue) + + async def _notify_tool_added(self, tool: DbTool) -> None: + """ + Notify subscribers of tool addition. + + Args: + tool: Tool added + """ + event = { + "type": "tool_added", + "data": { + "id": tool.id, + "name": tool.name, + "url": tool.url, + "description": tool.description, + "is_active": tool.is_active, + }, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _notify_tool_removed(self, tool: DbTool) -> None: + """ + Notify subscribers of tool removal (soft delete/deactivation). + + Args: + tool: Tool removed + """ + event = { + "type": "tool_removed", + "data": {"id": tool.id, "name": tool.name, "is_active": False}, + "timestamp": datetime.utcnow().isoformat(), + } + await self._publish_event(event) + + async def _publish_event(self, event: Dict[str, Any]) -> None: + """ + Publish event to all subscribers. + + Args: + event: Event to publish + """ + for queue in self._event_subscribers: + await queue.put(event) + + async def _validate_tool_url(self, url: str) -> None: + """Validate tool URL is accessible. + + Args: + url: URL to validate. + + Raises: + ToolValidationError: If URL validation fails. + """ + try: + response = await self._http_client.get(url) + response.raise_for_status() + except Exception as e: + raise ToolValidationError(f"Failed to validate tool URL: {str(e)}") + + async def _check_tool_health(self, tool: DbTool) -> bool: + """Check if tool endpoint is healthy. + + Args: + tool: Tool to check. + + Returns: + True if tool is healthy. + """ + try: + response = await self._http_client.get(tool.url) + return response.is_success + except Exception: + return False + + async def event_generator(self) -> AsyncGenerator[Dict[str, Any], None]: + """Generate tool events for SSE. + + Yields: + Tool events. + """ + queue: asyncio.Queue = asyncio.Queue() + self._event_subscribers.append(queue) + try: + while True: + event = await queue.get() + yield event + finally: + self._event_subscribers.remove(queue) + + # --- Metrics --- + async def aggregate_metrics(self, db: Session) -> Dict[str, Any]: + """ + Aggregate metrics for all tool invocations. + + Args: + db: Database session + + Returns: + A dictionary with keys: + - total_executions + - successful_executions + - failed_executions + - failure_rate + - min_response_time + - max_response_time + - avg_response_time + - last_execution_time + """ + + total = db.execute(select(func.count(ToolMetric.id))).scalar() or 0 # pylint: disable=not-callable + successful = db.execute(select(func.count(ToolMetric.id)).where(ToolMetric.is_success)).scalar() or 0 # pylint: disable=not-callable + failed = db.execute(select(func.count(ToolMetric.id)).where(not_(ToolMetric.is_success))).scalar() or 0 # pylint: disable=not-callable + failure_rate = failed / total if total > 0 else 0.0 + min_rt = db.execute(select(func.min(ToolMetric.response_time))).scalar() + max_rt = db.execute(select(func.max(ToolMetric.response_time))).scalar() + avg_rt = db.execute(select(func.avg(ToolMetric.response_time))).scalar() + last_time = db.execute(select(func.max(ToolMetric.timestamp))).scalar() + + return { + "total_executions": total, + "successful_executions": successful, + "failed_executions": failed, + "failure_rate": failure_rate, + "min_response_time": min_rt, + "max_response_time": max_rt, + "avg_response_time": avg_rt, + "last_execution_time": last_time, + } + + async def reset_metrics(self, db: Session, tool_id: Optional[int] = None) -> None: + """ + Reset metrics for tool invocations. + + If tool_id is provided, only the metrics for that specific tool will be deleted. + Otherwise, all tool metrics will be deleted (global reset). + + Args: + db (Session): The SQLAlchemy database session. + tool_id (Optional[int]): Specific tool ID to reset metrics for. + """ + + if tool_id: + db.execute(delete(ToolMetric).where(ToolMetric.tool_id == tool_id)) + else: + db.execute(delete(ToolMetric)) + db.commit() diff --git a/mcpgateway/static/admin.css b/mcpgateway/static/admin.css new file mode 100644 index 0000000000..5f3e656fa3 --- /dev/null +++ b/mcpgateway/static/admin.css @@ -0,0 +1,9 @@ +.error-status { + margin-top: 10px; + padding: 10px; + border-radius: 4px; + transition: opacity 0.3s ease; + background: #f2dede; + color: #a94442; + border: 1px solid #ebccd1; +} diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js new file mode 100644 index 0000000000..060bd7b75f --- /dev/null +++ b/mcpgateway/static/admin.js @@ -0,0 +1,1676 @@ +document.addEventListener("DOMContentLoaded", function () { + const hash = window.location.hash; + if (hash) { + showTab(hash.slice(1)); + } + + document.getElementById("tab-catalog").addEventListener("click", () => { + showTab("catalog"); + }); + document.getElementById("tab-tools").addEventListener("click", () => { + showTab("tools"); + }); + document.getElementById("tab-resources").addEventListener("click", () => { + showTab("resources"); + }); + document.getElementById("tab-prompts").addEventListener("click", () => { + showTab("prompts"); + }); + document.getElementById("tab-gateways").addEventListener("click", () => { + showTab("gateways"); + }); + document.getElementById("tab-roots").addEventListener("click", () => { + showTab("roots"); + }); + document.getElementById("tab-metrics").addEventListener("click", () => { + showTab("metrics"); + }); + // Authentication toggle + document.getElementById("auth-type").addEventListener("change", function () { + const basicFields = document.getElementById("auth-basic-fields"); + const bearerFields = document.getElementById("auth-bearer-fields"); + const headersFields = document.getElementById("auth-headers-fields"); + handleAuthTypeSelection( + this.value, + basicFields, + bearerFields, + headersFields, + ); + }); + document + .getElementById("auth-type-gw") + .addEventListener("change", function () { + const basicFields = document.getElementById("auth-basic-fields-gw"); + const bearerFields = document.getElementById("auth-bearer-fields-gw"); + const headersFields = document.getElementById("auth-headers-fields-gw"); + handleAuthTypeSelection( + this.value, + basicFields, + bearerFields, + headersFields, + ); + }); + document + .getElementById("auth-type-gw-edit") + .addEventListener("change", function () { + const basicFields = document.getElementById("auth-basic-fields-gw-edit"); + const bearerFields = document.getElementById( + "auth-bearer-fields-gw-edit", + ); + const headersFields = document.getElementById( + "auth-headers-fields-gw-edit", + ); + handleAuthTypeSelection( + this.value, + basicFields, + bearerFields, + headersFields, + ); + }); + document + .getElementById("edit-auth-type") + .addEventListener("change", function () { + const basicFields = document.getElementById("edit-auth-basic-fields"); + const bearerFields = document.getElementById("edit-auth-bearer-fields"); + const headersFields = document.getElementById("edit-auth-headers-fields"); + if (this.value === "basic") { + basicFields.style.display = "block"; + bearerFields.style.display = "none"; + headersFields.style.display = "none"; + } else if (this.value === "bearer") { + basicFields.style.display = "none"; + bearerFields.style.display = "block"; + headersFields.style.display = "none"; + } else if (this.value === "authheaders") { + basicFields.style.display = "none"; + bearerFields.style.display = "none"; + headersFields.style.display = "block"; + } else { + basicFields.style.display = "none"; + bearerFields.style.display = "none"; + headersFields.style.display = "none"; + } + }); + + document + .getElementById("add-gateway-form") + .addEventListener("submit", (e) => { + e.preventDefault(); + const form = e.target; + const formData = new FormData(form); + fetch(`${window.ROOT_PATH}/admin/gateways`, { + method: "POST", + body: formData, + }) + .then((response) => { + console.log(response); + if (!response.ok) { + const status = document.getElementById("status-gateways"); + status.textContent = "Connection failed!"; + status.classList.add("error-status"); + } else { + location.reload(); + } + }) + .catch((error) => { + console.error("Error:", error); + }); + }); + + document + .getElementById("add-resource-form") + .addEventListener("submit", (e) => { + e.preventDefault(); + const form = e.target; + const formData = new FormData(form); + fetch(`${window.ROOT_PATH}/admin/resources`, { + method: "POST", + body: formData, + }) + .then((response) => { + console.log(response); + if (!response.ok) { + const status = document.getElementById("status-resources"); + status.textContent = "Connection failed!"; + status.classList.add("error-status"); + } else { + location.reload(); + } + }) + .catch((error) => { + console.error("Error:", error); + }); + }); + + // Dynamically add parameter block on button click + document.getElementById("add-parameter-btn").addEventListener("click", () => { + parameterCount++; + const parametersContainer = document.getElementById("parameters-container"); + const paramDiv = document.createElement("div"); + paramDiv.classList.add( + "border", + "p-4", + "mb-4", + "rounded-md", + "bg-gray-50", + "shadow-sm", + ); + paramDiv.innerHTML = ` +

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ `; + parametersContainer.appendChild(paramDiv); + attachListeners(paramDiv); + updateSchemaPreview(); + + // Delete parameter functionality + const deleteButton = paramDiv.querySelector(".delete-param"); + deleteButton.addEventListener("click", () => { + paramDiv.remove(); + updateSchemaPreview(); + parameterCount--; + }); + }); + + // Let the form load and then refresh the code mirror editors + var addToolForm = document.getElementById("add-tool-form"); + addToolForm.addEventListener("click", function () { + if (getComputedStyle(addToolForm).display !== "none") { + refreshEditors(); + } + }); + + // for tools insertion failure pop ups + document + .getElementById("add-tool-form") + .addEventListener("submit", async function (event) { + event.preventDefault(); + // If in UI mode, update schemaEditor with generated schema + const mode = document.querySelector( + 'input[name="schema_input_mode"]:checked', + ).value; + if (mode === "ui") { + window.schemaEditor.setValue(generateSchema()); + } + // Save CodeMirror editors’ contents into the underlying textareas + if (window.headersEditor) { + window.headersEditor.save(); + } + if (window.schemaEditor) { + window.schemaEditor.save(); + } + + let formData = new FormData(this); + try { + let response = await fetch(`${window.ROOT_PATH}/admin/tools`, { + method: "POST", + body: formData, + }); + let result = await response.json(); + if (!result.success) { + alert(result.message || "An error occurred"); + } else { + window.location.href = "/admin#tools"; // Redirect on success + } + } catch (error) { + console.error("Fetch error:", error); + alert("Failed to submit the form. Check console for details."); + } + }); + + // You can override this default in HTML by adding `data-default="REST"` (for example) + const integrationTypeSelect = document.getElementById("integrationType"); + const defaultIntegration = + integrationTypeSelect.dataset.default || + integrationTypeSelect.options[0].value; + integrationTypeSelect.value = defaultIntegration; + updateRequestTypeOptions(); + + integrationTypeSelect.addEventListener("change", () => { + updateRequestTypeOptions(); + }); + + const editToolTypeSelect = document.getElementById("edit-tool-type"); + const editToolRequestTypeSelect = document.getElementById( + "edit-tool-request-type", + ); + + const requestTypeMap = { + MCP: ["SSE", "STDIO"], + REST: ["GET", "POST", "PUT", "DELETE"], + }; + + // Optionally pass in a pre-selected method + function updateEditToolRequestTypes(selectedMethod = null) { + const selectedType = editToolTypeSelect.value; + const allowedMethods = requestTypeMap[selectedType] || []; + + // Clear existing options + editToolRequestTypeSelect.innerHTML = ""; + + // Populate new options + allowedMethods.forEach((method) => { + const option = document.createElement("option"); + option.value = method; + option.textContent = method; + editToolRequestTypeSelect.appendChild(option); + }); + + // Set the pre-selected method, if valid + if (selectedMethod && allowedMethods.includes(selectedMethod)) { + editToolRequestTypeSelect.value = selectedMethod; + } + } + + // Call once on page load or when popup opens + const currentType = "REST"; // example: loaded from existing data + const currentMethod = "PUT"; // example: loaded from existing data + + editToolTypeSelect.value = currentType; + updateEditToolRequestTypes(currentMethod); + + // Update request type options when tool type changes + editToolTypeSelect.addEventListener("change", () => { + updateEditToolRequestTypes(); // no preselection on change + }); + + //Input schema UI backend for schema creation + window.schemaEditor = window.CodeMirror.fromTextArea( + document.getElementById("schema-editor"), + { + mode: "application/json", + theme: "monokai", + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + tabSize: 2, + }, + ); +}); + +// Tab handling +function showTab(tabName) { + document.querySelectorAll(".tab-panel").forEach((panel) => { + panel.classList.add("hidden"); + }); + document.querySelectorAll(".tab-link").forEach((link) => { + link.classList.remove("border-indigo-500", "text-indigo-600"); + link.classList.add("border-transparent", "text-gray-500"); + }); + document.getElementById(`${tabName}-panel`).classList.remove("hidden"); + document + .querySelector(`[href="#${tabName}"]`) + .classList.add("border-indigo-500", "text-indigo-600"); + document + .querySelector(`[href="#${tabName}"]`) + .classList.remove("border-transparent", "text-gray-500"); + + if (tabName === "metrics") { + loadAggregatedMetrics(); + } +} + +// handle auth type selection +function handleAuthTypeSelection( + value, + basicFields, + bearerFields, + headersFields, +) { + if (value === "basic") { + basicFields.style.display = "block"; + bearerFields.style.display = "none"; + headersFields.style.display = "none"; + } else if (value === "bearer") { + basicFields.style.display = "none"; + bearerFields.style.display = "block"; + headersFields.style.display = "none"; + } else if (value === "authheaders") { + basicFields.style.display = "none"; + bearerFields.style.display = "none"; + headersFields.style.display = "block"; + } else { + basicFields.style.display = "none"; + bearerFields.style.display = "none"; + headersFields.style.display = "none"; + } +} + +// Cached DOM elements +const schemaModeRadios = document.getElementsByName("schema_input_mode"); +const uiBuilderDiv = document.getElementById("ui-builder"); +const jsonInputContainer = document.getElementById("json-input-container"); +let parameterCount = 0; + +// Function to generate the JSON schema from UI builder inputs +function generateSchema() { + let schema = { + title: "CustomInputSchema", + type: "object", + properties: {}, + required: [], + }; + for (let i = 1; i <= parameterCount; i++) { + const nameField = document.querySelector(`[name="param_name_${i}"]`); + const typeField = document.querySelector(`[name="param_type_${i}"]`); + const descField = document.querySelector(`[name="param_description_${i}"]`); + const requiredField = document.querySelector( + `[name="param_required_${i}"]`, + ); + if (nameField && nameField.value.trim() !== "") { + schema.properties[nameField.value.trim()] = { + type: typeField.value, + description: descField.value.trim(), + }; + if (requiredField && requiredField.checked) { + schema.required.push(nameField.value.trim()); + } + } + } + return JSON.stringify(schema, null, 2); +} + +// Update CodeMirror editor with the generated schema +function updateSchemaPreview() { + const mode = document.querySelector( + 'input[name="schema_input_mode"]:checked', + ).value; + if (mode === "json") { + window.schemaEditor.setValue(generateSchema()); + } +} + +// Refresh CodeMirror every time Direct JSON Input is selected +Array.from(schemaModeRadios).forEach((radio) => { + radio.addEventListener("change", () => { + if (radio.value === "ui" && radio.checked) { + uiBuilderDiv.style.display = "block"; + jsonInputContainer.style.display = "none"; + } else if (radio.value === "json" && radio.checked) { + uiBuilderDiv.style.display = "none"; + jsonInputContainer.style.display = "block"; + updateSchemaPreview(); + } + }); +}); + +// Attach event listeners to dynamically added parameter inputs +function attachListeners(paramDiv) { + const inputs = paramDiv.querySelectorAll("input, select, textarea"); + inputs.forEach((input) => { + input.addEventListener("input", () => { + const mode = document.querySelector( + 'input[name="schema_input_mode"]:checked', + ).value; + if (mode === "json") { + updateSchemaPreview(); + } + }); + }); +} + +// On form submission, update CodeMirror with UI builder schema if needed +// document.getElementById('add-tool-form').addEventListener('submit', (e) => { +// const mode = document.querySelector('input[name="schema_input_mode"]:checked').value; +// if (mode === 'ui') { +// schemaEditor.setValue(generateSchema()); +// } +// }); + +// Function to toggle inactive items based on checkbox state +function toggleInactiveItems(type) { + const checkbox = document.getElementById(`show-inactive-${type}`); + const url = new URL(window.location); + if (checkbox.checked) { + url.searchParams.set("include_inactive", "true"); + } else { + url.searchParams.delete("include_inactive"); + } + window.location = url; +} + +// Tool CRUD operations +/** + * Fetches detailed tool information from the backend and renders all properties, + * including Request Type and Authentication details, in the tool detail modal. + * + * @param {number|string} toolId - The unique identifier of the tool. + */ +async function viewTool(toolId) { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/tools/${toolId}`); + const tool = await response.json(); + + let authHTML = ""; + + if (tool.auth?.username && tool.auth?.password) { + authHTML = ` +

Authentication Type: Basic

+

Username: ${tool.auth.username}

+

Password: ********

+ `; + } else if (tool.auth?.token) { + authHTML = ` +

Authentication Type: Token

+

Token: ********

+ `; + } else if (tool.auth?.authHeaderKey && tool.auth?.authHeaderValue) { + authHTML = ` +

Authentication Type: Custom Headers

+

Header Key: ${tool.auth.authHeaderKey}

+

Header Value: ********

+ `; + } else { + authHTML = `

Authentication Type: None

`; + } + + document.getElementById("tool-details").innerHTML = ` +
+

Name: ${tool.name}

+

URL: ${tool.url}

+

Type: ${tool.integrationType}

+

Description: ${tool.description || "N/A"}

+

Request Type: ${tool.requestType || "N/A"}

+ ${authHTML} +
+ Headers: +
${JSON.stringify(tool.headers || {}, null, 2)}
+
+
+ Input Schema: +
${JSON.stringify(tool.inputSchema || {}, null, 2)}
+
+
+ Metrics: +
    +
  • Total Executions: ${tool.metrics?.totalExecutions ?? 0}
  • +
  • Successful Executions: ${tool.metrics?.successfulExecutions ?? 0}
  • +
  • Failed Executions: ${tool.metrics?.failedExecutions ?? 0}
  • +
  • Failure Rate: ${tool.metrics?.failureRate ?? 0}
  • +
  • Min Response Time: ${tool.metrics?.minResponseTime ?? "N/A"}
  • +
  • Max Response Time: ${tool.metrics?.maxResponseTime ?? "N/A"}
  • +
  • Average Response Time: ${tool.metrics?.avgResponseTime ?? "N/A"}
  • +
  • Last Execution Time: ${tool.metrics?.lastExecutionTime ?? "N/A"}
  • +
+
+
+ `; + + openModal("tool-modal"); + } catch (error) { + console.error("Error fetching tool details:", error); + alert("Failed to load tool details"); + } +} + +/** + * Fetches tool details from the backend and populates the edit modal form, + * including Request Type and Authentication fields, so that they are pre-filled for editing. + * + * @param {number|string} toolId - The unique identifier of the tool to edit. + */ +async function editTool(toolId) { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/tools/${toolId}`); + const tool = await response.json(); + + // Set form action and populate basic fields. + document.getElementById("edit-tool-form").action = + `${window.ROOT_PATH}/admin/tools/${toolId}/edit`; + document.getElementById("edit-tool-name").value = tool.name; + document.getElementById("edit-tool-url").value = tool.url; + document.getElementById("edit-tool-description").value = + tool.description || ""; + document.getElementById("edit-tool-type").value = + tool.integrationType || "MCP"; + + // Populate authentication fields. + document.getElementById("edit-auth-type").value = tool.auth?.authType || ""; + if (tool.auth?.authType === "basic") { + document.getElementById("edit-auth-basic-fields").style.display = "block"; + document.getElementById("edit-auth-bearer-fields").style.display = "none"; + document.getElementById("edit-auth-headers-fields").style.display = + "none"; + document.getElementById("edit-auth-username").value = + tool.auth?.username || ""; + document.getElementById("edit-auth-password").value = + tool.auth?.password || ""; + } else if (tool.auth?.authType === "bearer") { + document.getElementById("edit-auth-basic-fields").style.display = "none"; + document.getElementById("edit-auth-bearer-fields").style.display = + "block"; + document.getElementById("edit-auth-headers-fields").style.display = + "none"; + document.getElementById("edit-auth-token").value = tool.auth.token || ""; + } else if (tool.auth?.authType === "authheaders") { + document.getElementById("edit-auth-basic-fields").style.display = "none"; + document.getElementById("edit-auth-bearer-fields").style.display = "none"; + document.getElementById("edit-auth-headers-fields").style.display = + "block"; + document.getElementById("edit-auth-key").value = + tool.auth?.authHeaderKey || ""; + document.getElementById("edit-auth-value").value = + tool.auth?.authHeaderValue || ""; + } else { + document.getElementById("edit-auth-basic-fields").style.display = "none"; + document.getElementById("edit-auth-bearer-fields").style.display = "none"; + document.getElementById("edit-auth-headers-fields").style.display = + "none"; + } + + const headersJson = JSON.stringify(tool.headers || {}, null, 2); + const schemaJson = JSON.stringify(tool.inputSchema || {}, null, 2); + + // Update the code editor textareas. + document.getElementById("edit-tool-headers").value = headersJson; + document.getElementById("edit-tool-schema").value = schemaJson; + if (window.editToolHeadersEditor) { + window.editToolHeadersEditor.setValue(headersJson); + window.editToolHeadersEditor.refresh(); + } + if (window.editToolSchemaEditor) { + window.editToolSchemaEditor.setValue(schemaJson); + window.editToolSchemaEditor.refresh(); + } + + const editToolTypeSelect = document.getElementById("edit-tool-type"); + const event = new Event("change"); + editToolTypeSelect.dispatchEvent(event); + + // Set Request Type field. + document.getElementById("edit-tool-request-type").value = + tool.requestType || "SSE"; + + openModal("tool-edit-modal"); + + // Ensure editors are refreshed after modal display. + setTimeout(() => { + if (window.editToolHeadersEditor) window.editToolHeadersEditor.refresh(); + if (window.editToolSchemaEditor) window.editToolSchemaEditor.refresh(); + }, 100); + } catch (error) { + console.error("Error fetching tool details:", error); + alert("Failed to load tool for editing"); + } +} + +async function viewResource(resourceUri) { + try { + const response = await fetch( + `${window.ROOT_PATH}/admin/resources/${encodeURIComponent(resourceUri)}`, + ); + const data = await response.json(); + const resource = data.resource; + const content = data.content; + document.getElementById("resource-details").innerHTML = ` +
+

URI: ${resource.uri}

+

Name: ${resource.name}

+

Type: ${resource.mimeType || "N/A"}

+

Description: ${resource.description || "N/A"}

+

Status: + + ${resource.isActive ? "Active" : "Inactive"} + +

+
+ Content: +
+                ${
+                  typeof content === "object"
+                    ? JSON.stringify(content, null, 2)
+                    : content
+                }
+              
+
+ +
+ Metrics: +
    +
  • Total Executions: ${resource.metrics.totalExecutions}
  • +
  • Successful Executions: ${resource.metrics.successfulExecutions}
  • +
  • Failed Executions: ${resource.metrics.failedExecutions}
  • +
  • Failure Rate: ${resource.metrics.failureRate}
  • +
  • Min Response Time: ${resource.metrics.minResponseTime}
  • +
  • Max Response Time: ${resource.metrics.maxResponseTime}
  • +
  • Average Response Time: ${resource.metrics.avgResponseTime}
  • +
  • Last Execution Time: ${resource.metrics.lastExecutionTime || "N/A"}
  • +
+
+
+ `; + openModal("resource-modal"); + } catch (error) { + console.error("Error fetching resource details:", error); + alert("Failed to load resource details"); + } +} + +async function editResource(resourceUri) { + try { + const response = await fetch( + `${window.ROOT_PATH}/admin/resources/${encodeURIComponent(resourceUri)}`, + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + const resource = data.resource; + // Set the form action for editing + document.getElementById("edit-resource-form").action = + `${window.ROOT_PATH}/admin/resources/${encodeURIComponent(resourceUri)}/edit`; + // Populate the fields using the returned resource object + document.getElementById("edit-resource-uri").value = resource.uri || ""; + document.getElementById("edit-resource-name").value = resource.name || ""; + document.getElementById("edit-resource-description").value = + resource.description || ""; + document.getElementById("edit-resource-mime-type").value = + resource.mimeType || ""; + const contentValue = + typeof data.content === "object" && data.content.text + ? data.content.text + : typeof data.content === "object" + ? JSON.stringify(data.content, null, 2) + : data.content || ""; + document.getElementById("edit-resource-content").value = contentValue; + if (window.editResourceContentEditor) { + window.editResourceContentEditor.setValue(contentValue); + window.editResourceContentEditor.refresh(); + } + openModal("resource-edit-modal"); + } catch (error) { + console.error("Error fetching resource details for editing:", error); + alert("Failed to load resource for editing"); + } +} + +async function viewPrompt(promptName) { + try { + const response = await fetch( + `${window.ROOT_PATH}/admin/prompts/${encodeURIComponent(promptName)}`, + ); + const prompt = await response.json(); + document.getElementById("prompt-details").innerHTML = ` +
+

Name: ${prompt.name}

+

Description: ${prompt.description || "N/A"}

+

Status: + + ${prompt.isActive ? "Active" : "Inactive"} + +

+
+ Template: +
+                ${prompt.template}
+              
+
+
+ Arguments: +
${JSON.stringify(prompt.arguments || [], null, 2)}
+
+ +
+ Metrics: +
    +
  • Total Executions: ${prompt.metrics.totalExecutions}
  • +
  • Successful Executions: ${prompt.metrics.successfulExecutions}
  • +
  • Failed Executions: ${prompt.metrics.failedExecutions}
  • +
  • Failure Rate: ${prompt.metrics.failureRate}
  • +
  • Min Response Time: ${prompt.metrics.minResponseTime}
  • +
  • Max Response Time: ${prompt.metrics.maxResponseTime}
  • +
  • Average Response Time: ${prompt.metrics.avgResponseTime}
  • +
  • Last Execution Time: ${prompt.metrics.lastExecutionTime || "N/A"}
  • +
+
+
+ `; + openModal("prompt-modal"); + } catch (error) { + console.error("Error fetching prompt details:", error); + alert("Failed to load prompt details"); + } +} + +async function editPrompt(promptName) { + try { + const response = await fetch( + `${window.ROOT_PATH}/admin/prompts/${encodeURIComponent(promptName)}`, + ); + const prompt = await response.json(); + document.getElementById("edit-prompt-form").action = + `${window.ROOT_PATH}/admin/prompts/${encodeURIComponent(promptName)}/edit`; + document.getElementById("edit-prompt-name").value = prompt.name; + document.getElementById("edit-prompt-description").value = + prompt.description || ""; + document.getElementById("edit-prompt-template").value = prompt.template; + document.getElementById("edit-prompt-arguments").value = JSON.stringify( + prompt.arguments || [], + null, + 2, + ); + if (window.editPromptTemplateEditor) { + window.editPromptTemplateEditor.setValue(prompt.template); + } + if (window.editPromptArgumentsEditor) { + window.editPromptArgumentsEditor.setValue( + JSON.stringify(prompt.arguments || [], null, 2), + ); + } + openModal("prompt-edit-modal"); + } catch (error) { + console.error("Error fetching prompt details:", error); + alert("Failed to load prompt for editing"); + } +} + +async function viewGateway(gatewayId) { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/gateways/${gatewayId}`); + const gateway = await response.json(); + + let authHTML = ""; + if (gateway.authUsername && gateway.authPassword) { + authHTML = ` +

Authentication Type: Basic

+

Username: ${gateway.authUsername}

+

Password: ********

+ `; + } else if (gateway.authToken) { + authHTML = ` +

Authentication Type: Bearer

+

Token: ********

+ `; + } else if (gateway.authHeaderKey && gateway.authHeaderValue) { + authHTML = ` +

Authentication Type: Custom Header

+

Header Key: ${gateway.authHeaderKey}

+

Header Value: ********

+ `; + } else { + authHTML = `

Authentication Type: None

`; + } + + document.getElementById("gateway-details").innerHTML = ` +
+

Name: ${gateway.name}

+

URL: ${gateway.url}

+

Description: ${gateway.description || "N/A"}

+

Status: + + ${gateway.isActive ? "Active" : "Inactive"} + +

+

Last Seen: ${gateway.lastSeen || "Never"}

+ ${authHTML} +
+ Capabilities: +
${JSON.stringify(gateway.capabilities || {}, null, 2)}
+
+
+ `; + + openModal("gateway-modal"); + } catch (error) { + console.error("Error fetching gateway details:", error); + alert("Failed to load gateway details"); + } +} + +async function editGateway(gatewayId) { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/gateways/${gatewayId}`); + const gateway = await response.json(); + document.getElementById("edit-gateway-form").action = + `${window.ROOT_PATH}/admin/gateways/${gatewayId}/edit`; + document.getElementById("edit-gateway-name").value = gateway.name; + document.getElementById("edit-gateway-url").value = gateway.url; + document.getElementById("edit-gateway-description").value = + gateway.description || ""; + openModal("gateway-edit-modal"); + } catch (error) { + console.error("Error fetching gateway details:", error); + alert("Failed to load gateway for editing"); + } +} + +// viewServer +async function viewServer(serverId) { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/servers/${serverId}`); + const server = await response.json(); + + // Helper function to render an associated item with ID and name + const renderAssociatedItem = (item, mapping) => { + // if the item is an object, use its properties directly + if (typeof item === "object") { + return ` + ${item.id}: ${item.name} + `; + } else { + // Otherwise, lookup the name using the mapping (fallback to the id itself) + const name = mapping[item] || item; + return ` + ${item}:${name} + `; + } + }; + + const toolsHTML = + Array.isArray(server.associatedTools) && server.associatedTools.length > 0 + ? server.associatedTools + .map((item) => renderAssociatedItem(item, window.toolMapping)) + .join(" ") + : "N/A"; + + const resourcesHTML = + Array.isArray(server.associatedResources) && + server.associatedResources.length > 0 + ? server.associatedResources + .map((item) => renderAssociatedItem(item, window.resourceMapping)) + .join(" ") + : "N/A"; + + const promptsHTML = + Array.isArray(server.associatedPrompts) && + server.associatedPrompts.length > 0 + ? server.associatedPrompts + .map((item) => renderAssociatedItem(item, window.promptMapping)) + .join(" ") + : "N/A"; + + document.getElementById("server-details").innerHTML = ` +
+

Name: ${server.name}

+

Description: ${server.description || "N/A"}

+

Status: + + ${server.isActive ? "Active" : "Inactive"} + +

+
+ Icon: + ${server.icon ? `${server.name} Icon` : "N/A"} +
+
+ Associated Tools: +
${toolsHTML}
+
+
+ Associated Resources: +
${resourcesHTML}
+
+
+ Associated Prompts: +
${promptsHTML}
+
+
+ Metrics: +
    +
  • Total Executions: ${server.metrics.totalExecutions}
  • +
  • Successful Executions: ${server.metrics.successfulExecutions}
  • +
  • Failed Executions: ${server.metrics.failedExecutions}
  • +
  • Failure Rate: ${server.metrics.failureRate}
  • +
  • Min Response Time: ${server.metrics.minResponseTime}
  • +
  • Max Response Time: ${server.metrics.maxResponseTime}
  • +
  • Average Response Time: ${server.metrics.avgResponseTime}
  • +
  • Last Execution Time: ${server.metrics.lastExecutionTime || "N/A"}
  • +
+
+
+ `; + openModal("server-modal"); + } catch (error) { + console.error("Error fetching server details:", error); + alert("Failed to load server details"); + } +} + +async function editServer(serverId) { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/servers/${serverId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const server = await response.json(); + // Set the form action for editing + document.getElementById("edit-server-form").action = + `${window.ROOT_PATH}/admin/servers/${serverId}/edit`; + // Fill in the basic fields + document.getElementById("edit-server-name").value = server.name || ""; + document.getElementById("edit-server-description").value = + server.description || ""; + document.getElementById("edit-server-icon").value = server.icon || ""; + // Fill in the associated tools field (already working) + document.getElementById("edit-server-tools").value = Array.isArray( + server.associatedTools, + ) + ? server.associatedTools.join(", ") + : ""; + // Fill in the associated resources field (new) + const resourcesField = document.getElementById("edit-server-resources"); + if (resourcesField) { + resourcesField.value = Array.isArray(server.associatedResources) + ? server.associatedResources.join(", ") + : ""; + } + // Fill in the associated prompts field (new) + const promptsField = document.getElementById("edit-server-prompts"); + if (promptsField) { + promptsField.value = Array.isArray(server.associatedPrompts) + ? server.associatedPrompts.join(", ") + : ""; + } + openModal("server-edit-modal"); + } catch (error) { + console.error("Error fetching server details for editing:", error); + alert("Failed to load server for editing"); + } +} + +// Initialize CodeMirror editors +document.addEventListener("DOMContentLoaded", function () { + window.headersEditor = window.CodeMirror.fromTextArea( + document.getElementById("headers-editor"), + { + mode: "application/json", + theme: "monokai", + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + tabSize: 2, + }, + ); + + window.resourceContentEditor = window.CodeMirror.fromTextArea( + document.getElementById("resource-content-editor"), + { + mode: "text/plain", + theme: "monokai", + lineNumbers: true, + tabSize: 2, + }, + ); + + window.promptTemplateEditor = window.CodeMirror.fromTextArea( + document.getElementById("prompt-template-editor"), + { + mode: "text/plain", + theme: "monokai", + lineNumbers: true, + tabSize: 2, + }, + ); + + window.promptArgsEditor = window.CodeMirror.fromTextArea( + document.getElementById("prompt-args-editor"), + { + mode: "application/json", + theme: "monokai", + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + tabSize: 2, + }, + ); + + window.editToolHeadersEditor = window.CodeMirror.fromTextArea( + document.getElementById("edit-tool-headers"), + { + mode: "application/json", + theme: "monokai", + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + tabSize: 2, + }, + ); + + window.editToolSchemaEditor = window.CodeMirror.fromTextArea( + document.getElementById("edit-tool-schema"), + { + mode: "application/json", + theme: "monokai", + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + tabSize: 2, + }, + ); + + window.editResourceContentEditor = window.CodeMirror.fromTextArea( + document.getElementById("edit-resource-content"), + { + mode: "text/plain", + theme: "monokai", + lineNumbers: true, + tabSize: 2, + }, + ); + + window.editPromptTemplateEditor = window.CodeMirror.fromTextArea( + document.getElementById("edit-prompt-template"), + { + mode: "text/plain", + theme: "monokai", + lineNumbers: true, + tabSize: 2, + }, + ); + + window.editPromptArgumentsEditor = window.CodeMirror.fromTextArea( + document.getElementById("edit-prompt-arguments"), + { + mode: "application/json", + theme: "monokai", + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + tabSize: 2, + }, + ); + + // Add event listener to save resource content before submitting the edit resource form + document + .getElementById("edit-resource-form") + .addEventListener("submit", function () { + if (window.editResourceContentEditor) { + window.editResourceContentEditor.save(); + } + }); + + // Set initial tab based on URL hash or default to Catalog + const hash = window.location.hash || "#catalog"; + showTab(hash.substring(1)); + + // Set checkbox states based on URL parameter + const urlParams = new URLSearchParams(window.location.search); + const includeInactive = urlParams.get("include_inactive") === "true"; + if (document.getElementById("show-inactive-tools")) + document.getElementById("show-inactive-tools").checked = includeInactive; + if (document.getElementById("show-inactive-resources")) + document.getElementById("show-inactive-resources").checked = + includeInactive; + if (document.getElementById("show-inactive-prompts")) + document.getElementById("show-inactive-prompts").checked = includeInactive; + if (document.getElementById("show-inactive-gateways")) + document.getElementById("show-inactive-gateways").checked = includeInactive; + if (document.getElementById("show-inactive-servers")) + document.getElementById("show-inactive-servers").checked = includeInactive; +}); + +function refreshEditors() { + // Use a timeout to let the browser render the form as visible + setTimeout(function () { + window.headersEditor.refresh(); + window.schemaEditor.refresh(); + }, 100); +} + +// +async function loadAggregatedMetrics() { + try { + // Fetch aggregated metrics from the backend + const response = await fetch(`${window.ROOT_PATH}/admin/metrics`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + // Use fallback for keys (snake_case vs camelCase) + const toolsTotal = + data.tools.total_executions ?? data.tools.totalExecutions ?? 0; + const toolsSuccess = + data.tools.successful_executions ?? data.tools.successfulExecutions ?? 0; + const toolsFailed = + data.tools.failed_executions ?? data.tools.failedExecutions ?? 0; + const toolsFailureRate = + data.tools.failure_rate ?? data.tools.failureRate ?? 0; + const toolsMin = + data.tools.min_response_time ?? data.tools.minResponseTime ?? "N/A"; + const toolsMax = + data.tools.max_response_time ?? data.tools.maxResponseTime ?? "N/A"; + const toolsAvg = + data.tools.avg_response_time ?? data.tools.avgResponseTime ?? "N/A"; + const toolsLast = + data.tools.last_execution_time ?? data.tools.lastExecutionTime ?? "N/A"; + + const resourcesTotal = + data.resources.totalExecutions ?? data.resources.total_executions ?? 0; + const resourcesSuccess = + data.resources.successfulExecutions ?? + data.resources.successful_executions ?? + 0; + const resourcesFailed = + data.resources.failedExecutions ?? data.resources.failed_executions ?? 0; + const resourcesFailureRate = + data.resources.failureRate ?? data.resources.failure_rate ?? 0; + const resourcesMin = + data.resources.minResponseTime ?? + data.resources.min_response_time ?? + "N/A"; + const resourcesMax = + data.resources.maxResponseTime ?? + data.resources.max_response_time ?? + "N/A"; + const resourcesAvg = + data.resources.avgResponseTime ?? + data.resources.avg_response_time ?? + "N/A"; + const resourcesLast = + data.resources.lastExecutionTime ?? + data.resources.last_execution_time ?? + "N/A"; + + const serversTotal = + data.servers.totalExecutions ?? data.servers.total_executions ?? 0; + const serversSuccess = + data.servers.successfulExecutions ?? + data.servers.successful_executions ?? + 0; + const serversFailed = + data.servers.failedExecutions ?? data.servers.failed_executions ?? 0; + const serversFailureRate = + data.servers.failureRate ?? data.servers.failure_rate ?? 0; + const serversMin = + data.servers.minResponseTime ?? data.servers.min_response_time ?? "N/A"; + const serversMax = + data.servers.maxResponseTime ?? data.servers.max_response_time ?? "N/A"; + const serversAvg = + data.servers.avgResponseTime ?? data.servers.avg_response_time ?? "N/A"; + const serversLast = + data.servers.lastExecutionTime ?? + data.servers.last_execution_time ?? + "N/A"; + + const promptsTotal = + data.prompts.total_executions ?? data.prompts.totalExecutions ?? 0; + const promptsSuccess = + data.prompts.successful_executions ?? + data.prompts.successfulExecutions ?? + 0; + const promptsFailed = + data.prompts.failed_executions ?? data.prompts.failedExecutions ?? 0; + const promptsFailureRate = + data.prompts.failure_rate ?? data.prompts.failureRate ?? 0; + const promptsMin = + data.prompts.min_response_time ?? data.prompts.minResponseTime ?? "N/A"; + const promptsMax = + data.prompts.max_response_time ?? data.prompts.maxResponseTime ?? "N/A"; + const promptsAvg = + data.prompts.avg_response_time ?? data.prompts.avgResponseTime ?? "N/A"; + const promptsLast = + data.prompts.last_execution_time ?? + data.prompts.lastExecutionTime ?? + "N/A"; + + // Build an aggregated metrics table + const tableHTML = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EntityTotalSuccessfulFailedFailure RateMin RTMax RTAvg RTLast Exec
Tools${toolsTotal}${toolsSuccess}${toolsFailed}${toolsFailureRate}${toolsMin}${toolsMax}${toolsAvg}${toolsLast}
Resources${resourcesTotal}${resourcesSuccess}${resourcesFailed}${resourcesFailureRate}${resourcesMin}${resourcesMax}${resourcesAvg}${resourcesLast}
Servers${serversTotal}${serversSuccess}${serversFailed}${serversFailureRate}${serversMin}${serversMax}${serversAvg}${serversLast}
Prompts${promptsTotal}${promptsSuccess}${promptsFailed}${promptsFailureRate}${promptsMin}${promptsMax}${promptsAvg}${promptsLast}
+ `; + document.getElementById("aggregated-metrics-content").innerHTML = tableHTML; + + // Update overall bar chart (Total Executions) + if (window.metricsChartInstance) { + window.metricsChartInstance.destroy(); + } + const ctx = document.getElementById("metricsChart").getContext("2d"); + window.metricsChartInstance = new window.Chart(ctx, { + type: "bar", + data: { + labels: ["Tools", "Resources", "Servers", "Prompts"], + datasets: [ + { + label: "Total Executions", + data: [toolsTotal, resourcesTotal, serversTotal, promptsTotal], + backgroundColor: [ + "rgba(54, 162, 235, 0.6)", + "rgba(75, 192, 192, 0.6)", + "rgba(255, 205, 86, 0.6)", + "rgba(201, 203, 207, 0.6)", + ], + borderColor: [ + "rgb(54, 162, 235)", + "rgb(75, 192, 192)", + "rgb(255, 205, 86)", + "rgb(201, 203, 207)", + ], + borderWidth: 1, + }, + ], + }, + options: { + scales: { + y: { + beginAtZero: true, + }, + }, + }, + }); + + // Now load top items for each category + loadTopTools(); + loadTopResources(); + loadTopServers(); + loadTopPrompts(); + } catch (error) { + console.error("Error fetching aggregated metrics:", error); + alert("Failed to load aggregated metrics"); + } +} + +async function loadTopTools() { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/tools`); + const tools = await response.json(); + // Sort descending by executions + tools.sort((a, b) => { + const aCount = a.metrics?.totalExecutions ?? a.executionCount ?? 0; + const bCount = b.metrics?.totalExecutions ?? b.executionCount ?? 0; + return bCount - aCount; + }); + const topTools = tools.slice(0, 5); + let html = ` + + + + + + + + `; + topTools.forEach((tool) => { + const count = tool.metrics?.totalExecutions ?? tool.executionCount ?? 0; + html += ` + + + + `; + }); + html += `
IDNameExecutions
${tool.id}${tool.name}${count}
`; + document.getElementById("top-tools-content").innerHTML = html; + } catch (error) { + console.error("Error loading top tools:", error); + document.getElementById("top-tools-content").innerHTML = + `

Error loading top tools.

`; + } +} + +async function loadTopResources() { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/resources`); + const resources = await response.json(); + resources.sort((a, b) => { + const aCount = a.metrics?.totalExecutions ?? 0; + const bCount = b.metrics?.totalExecutions ?? 0; + return bCount - aCount; + }); + const topResources = resources.slice(0, 5); + let html = ` + + + + + + + + + `; + topResources.forEach((resource) => { + const count = resource.metrics?.totalExecutions ?? 0; + html += ` + + + + + `; + }); + html += `
IDURINameExecutions
${resource.id}${resource.uri}${resource.name}${count}
`; + document.getElementById("top-resources-content").innerHTML = html; + } catch (error) { + console.error("Error loading top resources:", error); + document.getElementById("top-resources-content").innerHTML = + `

Error loading top resources.

`; + } +} + +async function loadTopServers() { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/servers`); + const servers = await response.json(); + servers.sort((a, b) => { + const aCount = a.metrics?.totalExecutions ?? 0; + const bCount = b.metrics?.totalExecutions ?? 0; + return bCount - aCount; + }); + const topServers = servers.slice(0, 5); + let html = ` + + + + + + + + `; + topServers.forEach((server) => { + const count = server.metrics?.totalExecutions ?? 0; + html += ` + + + + `; + }); + html += `
IDNameExecutions
${server.id}${server.name}${count}
`; + document.getElementById("top-servers-content").innerHTML = html; + } catch (error) { + console.error("Error loading top servers:", error); + document.getElementById("top-servers-content").innerHTML = + `

Error loading top servers.

`; + } +} + +async function loadTopPrompts() { + try { + const response = await fetch(`${window.ROOT_PATH}/admin/prompts`); + const prompts = await response.json(); + prompts.sort((a, b) => { + const aCount = a.metrics?.totalExecutions ?? 0; + const bCount = b.metrics?.totalExecutions ?? 0; + return bCount - aCount; + }); + const topPrompts = prompts.slice(0, 5); + let html = ` + + + + + + + + `; + topPrompts.forEach((prompt) => { + const count = prompt.metrics?.totalExecutions ?? 0; + html += ` + + + + `; + }); + html += `
IDNameExecutions
${prompt.id}${prompt.name}${count}
`; + document.getElementById("top-prompts-content").innerHTML = html; + } catch (error) { + console.error("Error loading top prompts:", error); + document.getElementById("top-prompts-content").innerHTML = + `

Error loading top prompts.

`; + } +} + +// Tool Test Modal +let currentTestTool = null; +let toolTestResultEditor = null; + +function testTool(toolId) { + // Fetch tool details from your backend (adjust the URL as needed) + fetch(`${window.ROOT_PATH}/admin/tools/${toolId}`) + .then((response) => response.json()) + .then((tool) => { + currentTestTool = tool; + // Use the tool's name as title and show its description (if available) + document.getElementById("tool-test-modal-title").innerText = + "Test Tool: " + tool.name; + document.getElementById("tool-test-modal-description").innerText = + tool.description || "No description available."; + + const container = document.getElementById("tool-test-form-fields"); + container.innerHTML = ""; // clear previous fields + + // Parse the input schema (assumed to be stored as a JSON string) + let schema = tool.inputSchema; + if (typeof schema === "string") { + try { + schema = JSON.parse(schema); + } catch (e) { + console.error("Invalid JSON schema", e); + schema = {}; + } + } + + // Dynamically create form fields based on schema.properties + if (schema.properties) { + for (let key in schema.properties) { + const prop = schema.properties[key]; + const fieldDiv = document.createElement("div"); + + // Field label + const label = document.createElement("label"); + label.innerText = key; + label.className = "block text-sm font-medium text-gray-700"; + fieldDiv.appendChild(label); + + // If a description exists, display it as help text + if (prop.description) { + const description = document.createElement("small"); + description.innerText = prop.description; + description.className = "text-gray-500 block mb-1"; + fieldDiv.appendChild(description); + } + + // Input field (default to text input) + const input = document.createElement("input"); + input.name = key; + input.type = "text"; + input.required = schema.required && schema.required.includes(key); + input.className = + "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"; + fieldDiv.appendChild(input); + + container.appendChild(fieldDiv); + } + } + openModal("tool-test-modal"); + }) + .catch((error) => { + console.error("Error fetching tool details for testing:", error); + alert("Failed to load tool details for testing."); + }); +} + +async function runToolTest() { + const form = document.getElementById("tool-test-form"); + const formData = new FormData(form); + const params = {}; + for (const [key, value] of formData.entries()) { + params[key] = value; + } + + // Build the JSON-RPC payload using the tool's name as the method + const payload = { + jsonrpc: "2.0", + id: Date.now(), + method: currentTestTool.name, + params: params, + }; + + // Send the request to your /rpc endpoint + fetch("/rpc", { + method: "POST", + headers: { + "Content-Type": "application/json", // ← make sure we include this + }, + body: JSON.stringify(payload), + credentials: "include", + }) + .then((response) => response.json()) + .then((result) => { + const resultStr = JSON.stringify(result, null, 2); + // If the CodeMirror editor exists, update its value; otherwise, create one. + if (toolTestResultEditor) { + toolTestResultEditor.setValue(resultStr); + } else { + toolTestResultEditor = window.CodeMirror( + document.getElementById("tool-test-result"), + { + value: resultStr, + mode: "application/json", + theme: "monokai", + readOnly: true, + lineNumbers: true, + }, + ); + } + }) + .catch((error) => { + document.getElementById("tool-test-result").innerText = "Error: " + error; + }); +} + +// Utility functions to open and close modals +function openModal(modalId) { + document.getElementById(modalId).classList.remove("hidden"); +} + +function closeModal(modalId) { + document.getElementById(modalId).classList.add("hidden"); +} + +const integrationRequestMap = { + MCP: ["SSE", "STDIO"], + REST: ["GET", "POST", "PUT", "DELETE"], +}; + +function updateRequestTypeOptions(preselectedValue = null) { + const requestTypeSelect = document.getElementById("requestType"); + const integrationTypeSelect = document.getElementById("integrationType"); + const selectedIntegration = integrationTypeSelect.value; + const options = integrationRequestMap[selectedIntegration] || []; + + // Clear current options + requestTypeSelect.innerHTML = ""; + + // Add new options + options.forEach((value) => { + const option = document.createElement("option"); + option.value = value; + option.textContent = value; + requestTypeSelect.appendChild(option); + }); + + // Set the value if preselected + if (preselectedValue && options.includes(preselectedValue)) { + requestTypeSelect.value = preselectedValue; + } +} + +window.toggleInactiveItems = toggleInactiveItems; +window.viewTool = viewTool; +window.editTool = editTool; +window.testTool = testTool; +window.viewResource = viewResource; +window.editResource = editResource; +window.viewPrompt = viewPrompt; +window.editPrompt = editPrompt; +window.viewGateway = viewGateway; +window.editGateway = editGateway; +window.viewServer = viewServer; +window.editServer = editServer; +window.runToolTest = runToolTest; +window.closeModal = closeModal; diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html new file mode 100644 index 0000000000..6cfaa37e52 --- /dev/null +++ b/mcpgateway/templates/admin.html @@ -0,0 +1,2465 @@ + + + + + + MCP Gateway Admin + + + + + + + + + + + +
+
+

+ MCP Context Forge - Gateway Administration +

+

+ Manage tools, resources, prompts, servers, and federated gateways +

+
+ + +
+ + +
+
+

MCP Servers Catalog

+
+ + +
+
+
+
+ + + + + + + + + + + + + + + {% for server in servers %} + + + + + + + + + + + {% endfor %} + +
+ Icon + + ID + + Name + + Description + + Tools + + Resources + + Prompts + + Actions +
+ {% if server.icon %} + {{ server.name }} Icon + {% else %} + N/A + {% endif %} + + {{ server.id }} + + {{ server.name }} + + {{ server.description }} + + {% if server.associatedTools %} {{ server.associatedTools | + map('string') | join(', ') }} {% else %} + N/A + {% endif %} + + {% if server.associatedResources %} {{ + server.associatedResources | join(', ') }} {% else %} + N/A + {% endif %} + + {% if server.associatedPrompts %} {{ + server.associatedPrompts | join(', ') }} {% else %} + N/A + {% endif %} + + {% if server.isActive %} +
+ + +
+ {% else %} +
+ + +
+ {% endif %} + + +
+ +
+
+
+
+ +
+

Add New Server

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mcpgateway/transports/__init__.py b/mcpgateway/transports/__init__.py new file mode 100644 index 0000000000..40752ec36b --- /dev/null +++ b/mcpgateway/transports/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +"""MCP Transport Package. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This package provides transport implementations for the MCP protocol: +- stdio: Communication over standard input/output +- SSE: Server-Sent Events for server-to-client streaming +- WebSocket: Full-duplex communication +""" + +from mcpgateway.transports.base import Transport +from mcpgateway.transports.sse_transport import SSETransport +from mcpgateway.transports.stdio_transport import StdioTransport +from mcpgateway.transports.websocket_transport import WebSocketTransport + +__all__ = ["Transport", "StdioTransport", "SSETransport", "WebSocketTransport"] diff --git a/mcpgateway/transports/base.py b/mcpgateway/transports/base.py new file mode 100644 index 0000000000..439c6f32b1 --- /dev/null +++ b/mcpgateway/transports/base.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +"""Base Transport Interface. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module defines the base protocol for MCP transports. +""" + +from abc import ABC, abstractmethod +from typing import Any, AsyncGenerator, Dict + + +class Transport(ABC): + """Base class for MCP transport implementations.""" + + @abstractmethod + async def connect(self) -> None: + """Initialize transport connection.""" + + @abstractmethod + async def disconnect(self) -> None: + """Close transport connection.""" + + @abstractmethod + async def send_message(self, message: Dict[str, Any]) -> None: + """Send a message over the transport. + + Args: + message: Message to send + """ + + @abstractmethod + async def receive_message(self) -> AsyncGenerator[Dict[str, Any], None]: + """Receive messages from the transport. + + Yields: + Received messages + """ + + @abstractmethod + async def is_connected(self) -> bool: + """Check if transport is connected. + + Returns: + True if connected + """ diff --git a/mcpgateway/transports/sse_transport.py b/mcpgateway/transports/sse_transport.py new file mode 100644 index 0000000000..a116622808 --- /dev/null +++ b/mcpgateway/transports/sse_transport.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +"""SSE Transport Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements Server-Sent Events (SSE) transport for MCP, +providing server-to-client streaming with proper session management. +""" + +import asyncio +import json +import logging +import uuid +from datetime import datetime +from typing import Any, AsyncGenerator, Dict + +from fastapi import Request +from sse_starlette.sse import EventSourceResponse + +from mcpgateway.config import settings +from mcpgateway.transports.base import Transport + +logger = logging.getLogger(__name__) + + +class SSETransport(Transport): + """Transport implementation using Server-Sent Events with proper session management.""" + + def __init__(self, base_url: str = None): + """Initialize SSE transport. + + Args: + base_url: Base URL for client message endpoints + """ + self._base_url = base_url or f"http://{settings.host}:{settings.port}" + self._connected = False + self._message_queue = asyncio.Queue() + self._client_gone = asyncio.Event() + self._session_id = str(uuid.uuid4()) + + logger.info(f"Creating SSE transport with base_url={self._base_url}, session_id={self._session_id}") + + async def connect(self) -> None: + """Set up SSE connection.""" + self._connected = True + logger.info(f"SSE transport connected: {self._session_id}") + + async def disconnect(self) -> None: + """Clean up SSE connection.""" + if self._connected: + self._connected = False + self._client_gone.set() + logger.info(f"SSE transport disconnected: {self._session_id}") + + async def send_message(self, message: Dict[str, Any]) -> None: + """Send a message over SSE. + + Args: + message: Message to send + + Raises: + RuntimeError: If transport is not connected + Exception: If unable to put message to queue + """ + if not self._connected: + raise RuntimeError("Transport not connected") + + try: + await self._message_queue.put(message) + logger.debug(f"Message queued for SSE: {self._session_id}, method={message.get('method', '(response)')}") + except Exception as e: + logger.error(f"Failed to queue message: {e}") + raise + + async def receive_message(self) -> AsyncGenerator[Dict[str, Any], None]: + """Receive messages from the client over SSE transport. + + This method implements a continuous message-receiving pattern for SSE transport. + Since SSE is primarily a server-to-client communication channel, this method + yields an initial initialize placeholder message and then enters a waiting loop. + The actual client messages are received via a separate HTTP POST endpoint + (not handled in this method). + + The method will continue running until either: + 1. The connection is explicitly disconnected (client_gone event is set) + 2. The receive loop is cancelled from outside + + Yields: + Dict[str, Any]: JSON-RPC formatted messages. The first yielded message is always + an initialize placeholder with the format: + {"jsonrpc": "2.0", "method": "initialize", "id": 1} + + Raises: + RuntimeError: If the transport is not connected when this method is called + asyncio.CancelledError: When the SSE receive loop is cancelled externally + """ + if not self._connected: + raise RuntimeError("Transport not connected") + + # For SSE, we set up a loop to wait for messages which are delivered via POST + # Most messages come via the POST endpoint, but we yield an initial initialize placeholder + # to keep the receive loop running + yield {"jsonrpc": "2.0", "method": "initialize", "id": 1} + + # Continue waiting for cancellation + try: + while not self._client_gone.is_set(): + await asyncio.sleep(1.0) + except asyncio.CancelledError: + logger.info(f"SSE receive loop cancelled for session {self._session_id}") + raise + finally: + logger.info(f"SSE receive loop ended for session {self._session_id}") + + async def is_connected(self) -> bool: + """Check if transport is connected. + + Returns: + True if connected + """ + return self._connected + + async def create_sse_response(self, _request: Request) -> EventSourceResponse: + """Create SSE response for streaming. + + Args: + request: FastAPI request + + Returns: + SSE response object + """ + endpoint_url = f"{self._base_url}/message?session_id={self._session_id}" + + async def event_generator(): + """Generate SSE events. + + Yields: + SSE event + """ + # Send the endpoint event first + yield { + "event": "endpoint", + "data": endpoint_url, + "retry": settings.sse_retry_timeout, + } + + # Send keepalive immediately to help establish connection + yield { + "event": "keepalive", + "data": "{}", + "retry": settings.sse_retry_timeout, + } + + try: + while not self._client_gone.is_set(): + try: + # Wait for messages with a timeout for keepalives + message = await asyncio.wait_for( + self._message_queue.get(), + timeout=30.0, # 30 second timeout for keepalives (some tools require more timeout for execution) + ) + + data = json.dumps(message, default=lambda obj: (obj.strftime("%Y-%m-%d %H:%M:%S") if isinstance(obj, datetime) else TypeError("Type not serializable"))) + + # logger.info(f"Sending SSE message: {data[:100]}...") + logger.debug(f"Sending SSE message: {data}") + + yield { + "event": "message", + "data": data, + "retry": settings.sse_retry_timeout, + } + except asyncio.TimeoutError: + # Send keepalive on timeout + yield { + "event": "keepalive", + "data": "{}", + "retry": settings.sse_retry_timeout, + } + except Exception as e: + logger.error(f"Error processing SSE message: {e}") + yield { + "event": "error", + "data": json.dumps({"error": str(e)}), + "retry": settings.sse_retry_timeout, + } + except asyncio.CancelledError: + logger.info(f"SSE event generator cancelled: {self._session_id}") + except Exception as e: + logger.error(f"SSE event generator error: {e}") + finally: + logger.info(f"SSE event generator completed: {self._session_id}") + # We intentionally don't set client_gone here to allow queued messages to be processed + + return EventSourceResponse( + event_generator(), + status_code=200, + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "Content-Type": "text/event-stream", + "X-MCP-SSE": "true", + }, + ) + + async def _client_disconnected(self, _request: Request) -> bool: + """Check if client has disconnected. + + Args: + request: FastAPI Request object + + Returns: + bool: True if client disconnected + """ + # We only check our internal client_gone flag + # We intentionally don't check connection_lost on the request + # as it can be unreliable and cause premature closures + return self._client_gone.is_set() + + @property + def session_id(self) -> str: + """ + Get the session ID for this transport. + + Returns: + str: session_id + """ + return self._session_id diff --git a/mcpgateway/transports/stdio_transport.py b/mcpgateway/transports/stdio_transport.py new file mode 100644 index 0000000000..9ec7e4384b --- /dev/null +++ b/mcpgateway/transports/stdio_transport.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +"""stdio Transport Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements stdio transport for MCP, handling +communication over standard input/output streams. +""" + +import asyncio +import json +import logging +import sys +from typing import Any, AsyncGenerator, Dict, Optional + +from mcpgateway.transports.base import Transport + +logger = logging.getLogger(__name__) + + +class StdioTransport(Transport): + """Transport implementation using stdio streams.""" + + def __init__(self): + """Initialize stdio transport.""" + self._stdin_reader: Optional[asyncio.StreamReader] = None + self._stdout_writer: Optional[asyncio.StreamWriter] = None + self._connected = False + + async def connect(self) -> None: + """Set up stdio streams.""" + loop = asyncio.get_running_loop() + + # Set up stdin reader + reader = asyncio.StreamReader() + protocol = asyncio.StreamReaderProtocol(reader) + await loop.connect_read_pipe(lambda: protocol, sys.stdin) + self._stdin_reader = reader + + # Set up stdout writer + transport, protocol = await loop.connect_write_pipe(asyncio.streams.FlowControlMixin, sys.stdout) + self._stdout_writer = asyncio.StreamWriter(transport, protocol, reader, loop) + + self._connected = True + logger.info("stdio transport connected") + + async def disconnect(self) -> None: + """Clean up stdio streams.""" + if self._stdout_writer: + self._stdout_writer.close() + await self._stdout_writer.wait_closed() + self._connected = False + logger.info("stdio transport disconnected") + + async def send_message(self, message: Dict[str, Any]) -> None: + """Send a message over stdout. + + Args: + message: Message to send + + Raises: + RuntimeError: If transport is not connected + Exception: If unable to write to stdio writer + """ + if not self._stdout_writer: + raise RuntimeError("Transport not connected") + + try: + data = json.dumps(message) + self._stdout_writer.write(f"{data}\n".encode()) + await self._stdout_writer.drain() + except Exception as e: + logger.error(f"Failed to send message: {e}") + raise + + async def receive_message(self) -> AsyncGenerator[Dict[str, Any], None]: + """Receive messages from stdin. + + Yields: + Received messages + + Raises: + RuntimeError: If transport is not connected + """ + if not self._stdin_reader: + raise RuntimeError("Transport not connected") + + while True: + try: + # Read line from stdin + line = await self._stdin_reader.readline() + if not line: + break + + # Parse JSON message + message = json.loads(line.decode().strip()) + yield message + + except asyncio.CancelledError: + break + except Exception as e: + logger.error(f"Failed to receive message: {e}") + continue + + async def is_connected(self) -> bool: + """Check if transport is connected. + + Returns: + True if connected + """ + return self._connected diff --git a/mcpgateway/transports/websocket_transport.py b/mcpgateway/transports/websocket_transport.py new file mode 100644 index 0000000000..6177b58cfc --- /dev/null +++ b/mcpgateway/transports/websocket_transport.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +"""WebSocket Transport Implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module implements WebSocket transport for MCP, providing +full-duplex communication between client and server. +""" + +import asyncio +import logging +from typing import Any, AsyncGenerator, Dict, Optional + +from fastapi import WebSocket, WebSocketDisconnect + +from mcpgateway.config import settings +from mcpgateway.transports.base import Transport + +logger = logging.getLogger(__name__) + + +class WebSocketTransport(Transport): + """Transport implementation using WebSocket.""" + + def __init__(self, websocket: WebSocket): + """Initialize WebSocket transport. + + Args: + websocket: FastAPI WebSocket connection + """ + self._websocket = websocket + self._connected = False + self._ping_task: Optional[asyncio.Task] = None + + async def connect(self) -> None: + """Set up WebSocket connection.""" + await self._websocket.accept() + self._connected = True + + # Start ping task + if settings.websocket_ping_interval > 0: + self._ping_task = asyncio.create_task(self._ping_loop()) + + logger.info("WebSocket transport connected") + + async def disconnect(self) -> None: + """Clean up WebSocket connection.""" + if self._ping_task: + self._ping_task.cancel() + try: + await self._ping_task + except asyncio.CancelledError: + pass + + if self._connected: + await self._websocket.close() + self._connected = False + logger.info("WebSocket transport disconnected") + + async def send_message(self, message: Dict[str, Any]) -> None: + """Send a message over WebSocket. + + Args: + message: Message to send + + Raises: + RuntimeError: If transport is not connected + Exception: If unable to send json to websocket + """ + if not self._connected: + raise RuntimeError("Transport not connected") + + try: + await self._websocket.send_json(message) + except Exception as e: + logger.error(f"Failed to send message: {e}") + raise + + async def receive_message(self) -> AsyncGenerator[Dict[str, Any], None]: + """Receive messages from WebSocket. + + Yields: + Received messages + + Raises: + RuntimeError: If transport is not connected + """ + if not self._connected: + raise RuntimeError("Transport not connected") + + try: + while True: + message = await self._websocket.receive_json() + yield message + + except WebSocketDisconnect: + logger.info("WebSocket client disconnected") + self._connected = False + except Exception as e: + logger.error(f"Error receiving message: {e}") + self._connected = False + finally: + await self.disconnect() + + async def is_connected(self) -> bool: + """Check if transport is connected. + + Returns: + True if connected + """ + return self._connected + + async def _ping_loop(self) -> None: + """Send periodic ping messages to keep connection alive.""" + try: + while self._connected: + await asyncio.sleep(settings.websocket_ping_interval) + await self._websocket.send_bytes(b"ping") + try: + resp = await asyncio.wait_for( + self._websocket.receive_bytes(), + timeout=settings.websocket_ping_interval / 2, + ) + if resp != b"pong": + logger.warning("Invalid ping response") + except asyncio.TimeoutError: + logger.warning("Ping timeout") + break + except Exception as e: + logger.error(f"Ping loop error: {e}") + finally: + await self.disconnect() + + async def send_ping(self) -> None: + """Send a manual ping message.""" + if self._connected: + await self._websocket.send_bytes(b"ping") diff --git a/mcpgateway/types.py b/mcpgateway/types.py new file mode 100644 index 0000000000..f3420bee2d --- /dev/null +++ b/mcpgateway/types.py @@ -0,0 +1,745 @@ +# -*- coding: utf-8 -*- +"""MCP Protocol Type Definitions. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module defines all core MCP protocol types according to the specification. +It includes: + - Message content types (text, image, resource) + - Tool definitions and schemas + - Resource types and templates + - Prompt structures + - Protocol initialization types + - Sampling message types + - Capability definitions +""" + +from datetime import datetime +from enum import Enum +from typing import Any, Dict, List, Literal, Optional, Union + +from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field + + +class Role(str, Enum): + """Message role in conversations. + + Attributes: + ASSISTANT (str): Indicates the assistant's role. + USER (str): Indicates the user's role. + """ + + ASSISTANT = "assistant" + USER = "user" + + +class LogLevel(str, Enum): + """Standard syslog severity levels as defined in RFC 5424. + + Attributes: + DEBUG (str): Debug level. + INFO (str): Informational level. + NOTICE (str): Notice level. + WARNING (str): Warning level. + ERROR (str): Error level. + CRITICAL (str): Critical level. + ALERT (str): Alert level. + EMERGENCY (str): Emergency level. + """ + + DEBUG = "debug" + INFO = "info" + NOTICE = "notice" + WARNING = "warning" + ERROR = "error" + CRITICAL = "critical" + ALERT = "alert" + EMERGENCY = "emergency" + + +# Base content types +class TextContent(BaseModel): + """Text content for messages. + + Attributes: + type (Literal["text"]): The fixed content type identifier for text. + text (str): The actual text message. + """ + + type: Literal["text"] + text: str + + +class JSONContent(BaseModel): + """JSON content for messages. + Attributes: + type (Literal["text"]): The fixed content type identifier for text. + json (dict): The actual text message. + """ + + type: Literal["text"] + text: dict + + +class ImageContent(BaseModel): + """Image content for messages. + + Attributes: + type (Literal["image"]): The fixed content type identifier for images. + data (bytes): The binary data of the image. + mime_type (str): The MIME type (e.g. "image/png") of the image. + """ + + type: Literal["image"] + data: bytes + mime_type: str + + +class ResourceContent(BaseModel): + """Resource content that can be embedded. + + Attributes: + type (Literal["resource"]): The fixed content type identifier for resources. + uri (str): The URI identifying the resource. + mime_type (Optional[str]): The MIME type of the resource, if known. + text (Optional[str]): A textual representation of the resource, if applicable. + blob (Optional[bytes]): Binary data of the resource, if applicable. + """ + + type: Literal["resource"] + uri: str + mime_type: Optional[str] = None + text: Optional[str] = None + blob: Optional[bytes] = None + + +ContentType = Union[TextContent, JSONContent, ImageContent, ResourceContent] + + +# Reference types - needed early for completion +class PromptReference(BaseModel): + """Reference to a prompt or prompt template. + + Attributes: + type (Literal["ref/prompt"]): The fixed reference type identifier for prompts. + name (str): The unique name of the prompt. + """ + + type: Literal["ref/prompt"] + name: str + + +class ResourceReference(BaseModel): + """Reference to a resource or resource template. + + Attributes: + type (Literal["ref/resource"]): The fixed reference type identifier for resources. + uri (str): The URI of the resource. + """ + + type: Literal["ref/resource"] + uri: str + + +# Completion types +class CompleteRequest(BaseModel): + """Request for completion suggestions. + + Attributes: + ref (Union[PromptReference, ResourceReference]): A reference to a prompt or resource. + argument (Dict[str, str]): A dictionary containing arguments for the completion. + """ + + ref: Union[PromptReference, ResourceReference] + argument: Dict[str, str] + + +class CompleteResult(BaseModel): + """Result for a completion request. + + Attributes: + completion (Dict[str, Any]): A dictionary containing the completion results. + """ + + completion: Dict[str, Any] = Field(..., description="Completion results") + + +# Implementation info +class Implementation(BaseModel): + """MCP implementation information. + + Attributes: + name (str): The name of the implementation. + version (str): The version of the implementation. + """ + + name: str + version: str + + +# Model preferences +class ModelHint(BaseModel): + """Hint for model selection. + + Attributes: + name (Optional[str]): An optional hint for the model name. + """ + + name: Optional[str] = None + + +class ModelPreferences(BaseModel): + """Server preferences for model selection. + + Attributes: + cost_priority (float): Priority for cost efficiency (0 to 1). + speed_priority (float): Priority for speed (0 to 1). + intelligence_priority (float): Priority for intelligence (0 to 1). + hints (List[ModelHint]): A list of model hints. + """ + + cost_priority: float = Field(ge=0, le=1) + speed_priority: float = Field(ge=0, le=1) + intelligence_priority: float = Field(ge=0, le=1) + hints: List[ModelHint] = [] + + +# Capability types +class ClientCapabilities(BaseModel): + """Capabilities that a client may support. + + Attributes: + roots (Optional[Dict[str, bool]]): Capabilities related to root management. + sampling (Optional[Dict[str, Any]]): Capabilities related to LLM sampling. + experimental (Optional[Dict[str, Dict[str, Any]]]): Experimental capabilities. + """ + + roots: Optional[Dict[str, bool]] = None + sampling: Optional[Dict[str, Any]] = None + experimental: Optional[Dict[str, Dict[str, Any]]] = None + + +class ServerCapabilities(BaseModel): + """Capabilities that a server may support. + + Attributes: + prompts (Optional[Dict[str, bool]]): Capability for prompt support. + resources (Optional[Dict[str, bool]]): Capability for resource support. + tools (Optional[Dict[str, bool]]): Capability for tool support. + logging (Optional[Dict[str, Any]]): Capability for logging support. + experimental (Optional[Dict[str, Dict[str, Any]]]): Experimental capabilities. + """ + + prompts: Optional[Dict[str, bool]] = None + resources: Optional[Dict[str, bool]] = None + tools: Optional[Dict[str, bool]] = None + logging: Optional[Dict[str, Any]] = None + experimental: Optional[Dict[str, Dict[str, Any]]] = None + + +# Initialization types +class InitializeRequest(BaseModel): + """Initial request sent from the client to the server. + + Attributes: + protocol_version (str): The protocol version (alias: protocolVersion). + capabilities (ClientCapabilities): The client's capabilities. + client_info (Implementation): The client's implementation information (alias: clientInfo). + + Note: + The alias settings allow backward compatibility with older Pydantic versions. + """ + + protocol_version: str = Field(..., alias="protocolVersion") + capabilities: ClientCapabilities + client_info: Implementation = Field(..., alias="clientInfo") + + class Config: + """Configuration for InitializeRequest. + + Attributes: + populate_by_name (bool): Enables population by field name. + allow_population_by_field_name (bool): Allows backward compatibility with older Pydantic versions. + """ + + populate_by_name = True + allow_population_by_field_name = True # Use this for backward compatibility with older Pydantic versions + + +class InitializeResult(BaseModel): + """Server's response to the initialization request. + + Attributes: + protocol_version (str): The protocol version used. + capabilities (ServerCapabilities): The server's capabilities. + server_info (Implementation): The server's implementation information. + instructions (Optional[str]): Optional instructions for the client. + """ + + protocol_version: str = Field(..., alias="protocolVersion") + capabilities: ServerCapabilities = Field(..., alias="capabilities") + server_info: Implementation = Field(..., alias="serverInfo") + instructions: Optional[str] = Field(None, alias="instructions") + + class Config: + """ + Configuration class for Pydantic models. + + Enables population of model fields by name and by field name. + """ + + populate_by_name = True + allow_population_by_field_name = True + + +# Message types +class Message(BaseModel): + """A message in a conversation. + + Attributes: + role (Role): The role of the message sender. + content (ContentType): The content of the message. + """ + + role: Role + content: ContentType + + +class SamplingMessage(BaseModel): + """A message used in LLM sampling requests. + + Attributes: + role (Role): The role of the sender. + content (ContentType): The content of the sampling message. + """ + + role: Role + content: ContentType + + +# Sampling types for the client features +class CreateMessageResult(BaseModel): + """Result from a sampling/createMessage request. + + Attributes: + content (Union[TextContent, ImageContent]): The generated content. + model (str): The model used for generating the content. + role (Role): The role associated with the content. + stop_reason (Optional[str]): An optional reason for why sampling stopped. + """ + + content: Union[TextContent, ImageContent] + model: str + role: Role + stop_reason: Optional[str] = None + + +# Prompt types +class PromptArgument(BaseModel): + """An argument that can be passed to a prompt. + + Attributes: + name (str): The name of the argument. + description (Optional[str]): An optional description of the argument. + required (bool): Whether the argument is required. Defaults to False. + """ + + name: str + description: Optional[str] = None + required: bool = False + + +class Prompt(BaseModel): + """A prompt template offered by the server. + + Attributes: + name (str): The unique name of the prompt. + description (Optional[str]): A description of the prompt. + arguments (List[PromptArgument]): A list of expected prompt arguments. + """ + + name: str + description: Optional[str] = None + arguments: List[PromptArgument] = [] + + +class PromptResult(BaseModel): + """Result of rendering a prompt template. + + Attributes: + messages (List[Message]): The list of messages produced by rendering the prompt. + description (Optional[str]): An optional description of the rendered result. + """ + + messages: List[Message] + description: Optional[str] = None + + +# Tool types +class Tool(BaseModel): + """A tool that can be invoked. + + Attributes: + name (str): The unique name of the tool. + url (AnyHttpUrl): The URL of the tool. + description (Optional[str]): A description of the tool. + integrationType (str): The integration type of the tool (e.g. MCP or REST). + requestType (str): The HTTP method used to invoke the tool (GET, POST, PUT, DELETE, SSE, STDIO). + headers (Dict[str, Any]): A JSON object representing HTTP headers. + input_schema (Dict[str, Any]): A JSON Schema for validating the tool's input. + auth_type (Optional[str]): The type of authentication used ("basic", "bearer", or None). + auth_username (Optional[str]): The username for basic authentication. + auth_password (Optional[str]): The password for basic authentication. + auth_token (Optional[str]): The token for bearer authentication. + """ + + name: str + url: AnyHttpUrl + description: Optional[str] = None + integrationType: str = "MCP" + requestType: str = "SSE" + headers: Dict[str, Any] = Field(default_factory=dict) + input_schema: Dict[str, Any] = Field(default_factory=lambda: {"type": "object", "properties": {}}) + auth_type: Optional[str] = None + auth_username: Optional[str] = None + auth_password: Optional[str] = None + auth_token: Optional[str] = None + + +class ToolResult(BaseModel): + """Result of a tool invocation. + + Attributes: + content (List[ContentType]): A list of content items returned by the tool. + is_error (bool): Flag indicating if the tool call resulted in an error. + """ + + content: List[ContentType] + is_error: bool = False + + +# Resource types +class Resource(BaseModel): + """A resource available from the server. + + Attributes: + uri (str): The unique URI of the resource. + name (str): The human-readable name of the resource. + description (Optional[str]): A description of the resource. + mime_type (Optional[str]): The MIME type of the resource. + size (Optional[int]): The size of the resource. + """ + + uri: str + name: str + description: Optional[str] = None + mime_type: Optional[str] = None + size: Optional[int] = None + + +class ResourceTemplate(BaseModel): + """A template for constructing resource URIs. + + Attributes: + uri_template (str): The URI template string. + name (str): The unique name of the template. + description (Optional[str]): A description of the template. + mime_type (Optional[str]): The MIME type associated with the template. + """ + + uri_template: str + name: str + description: Optional[str] = None + mime_type: Optional[str] = None + + +class ListResourceTemplatesResult(BaseModel): + """The server's response to a resources/templates/list request from the client. + + Attributes: + meta (Optional[Dict[str, Any]]): Reserved property for metadata. + next_cursor (Optional[str]): Pagination cursor for the next page of results. + resource_templates (List[ResourceTemplate]): List of resource templates. + """ + + meta: Optional[Dict[str, Any]] = Field( + None, alias="_meta", description="This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses." + ) + next_cursor: Optional[str] = Field(None, description="An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.") + resource_templates: List[ResourceTemplate] = Field(default_factory=list, description="List of resource templates available on the server") + + class Config: + """Configuration for model serialization.""" + + populate_by_name = True + allow_population_by_field_name = True + + +# Root types +class FileUrl(AnyUrl): + """A specialized URL for file resources. + + This URL accepts only the "file" scheme and does not require a host. + """ + + allowed_schemes = {"file"} + host_required = False + + +class Root(BaseModel): + """A root directory or file. + + Attributes: + uri (Union[FileUrl, AnyUrl]): The unique identifier for the root. + name (Optional[str]): An optional human-readable name. + """ + + uri: Union[FileUrl, AnyUrl] = Field(..., description="Unique identifier for the root") + name: Optional[str] = Field(None, description="Optional human-readable name") + + +# Progress types +class ProgressToken(BaseModel): + """Token for associating progress notifications. + + Attributes: + value (Union[str, int]): The token value. + """ + + value: Union[str, int] + + +class Progress(BaseModel): + """Progress update for long-running operations. + + Attributes: + progress_token (ProgressToken): The token associated with the progress update. + progress (float): The current progress value. + total (Optional[float]): The total progress value, if known. + """ + + progress_token: ProgressToken + progress: float + total: Optional[float] = None + + +# JSON-RPC types +class JSONRPCRequest(BaseModel): + """JSON-RPC 2.0 request. + + Attributes: + jsonrpc (Literal["2.0"]): The JSON-RPC version. + id (Optional[Union[str, int]]): The request identifier. + method (str): The method name. + params (Optional[Dict[str, Any]]): The parameters for the request. + """ + + jsonrpc: Literal["2.0"] + id: Optional[Union[str, int]] = None + method: str + params: Optional[Dict[str, Any]] = None + + +class JSONRPCResponse(BaseModel): + """JSON-RPC 2.0 response. + + Attributes: + jsonrpc (Literal["2.0"]): The JSON-RPC version. + id (Optional[Union[str, int]]): The request identifier. + result (Optional[Any]): The result of the request. + error (Optional[Dict[str, Any]]): The error object if an error occurred. + """ + + jsonrpc: Literal["2.0"] + id: Optional[Union[str, int]] = None + result: Optional[Any] = None + error: Optional[Dict[str, Any]] = None + + +class JSONRPCError(BaseModel): + """JSON-RPC 2.0 error. + + Attributes: + code (int): The error code. + message (str): A short description of the error. + data (Optional[Any]): Additional data about the error. + """ + + code: int + message: str + data: Optional[Any] = None + + +# Transport message types +class SSEEvent(BaseModel): + """Server-Sent Events message. + + Attributes: + id (Optional[str]): The event identifier. + event (Optional[str]): The event type. + data (str): The event data. + retry (Optional[int]): The retry timeout in milliseconds. + """ + + id: Optional[str] = None + event: Optional[str] = None + data: str + retry: Optional[int] = None + + +class WebSocketMessage(BaseModel): + """WebSocket protocol message. + + Attributes: + type (str): The type of the WebSocket message. + data (Any): The message data. + """ + + type: str + data: Any + + +# Notification types +class ResourceUpdateNotification(BaseModel): + """Notification of resource changes. + + Attributes: + method (Literal["notifications/resources/updated"]): The notification method. + uri (str): The URI of the updated resource. + """ + + method: Literal["notifications/resources/updated"] + uri: str + + +class ResourceListChangedNotification(BaseModel): + """Notification of resource list changes. + + Attributes: + method (Literal["notifications/resources/list_changed"]): The notification method. + """ + + method: Literal["notifications/resources/list_changed"] + + +class PromptListChangedNotification(BaseModel): + """Notification of prompt list changes. + + Attributes: + method (Literal["notifications/prompts/list_changed"]): The notification method. + """ + + method: Literal["notifications/prompts/list_changed"] + + +class ToolListChangedNotification(BaseModel): + """Notification of tool list changes. + + Attributes: + method (Literal["notifications/tools/list_changed"]): The notification method. + """ + + method: Literal["notifications/tools/list_changed"] + + +class CancelledNotification(BaseModel): + """Notification of request cancellation. + + Attributes: + method (Literal["notifications/cancelled"]): The notification method. + request_id (Union[str, int]): The ID of the cancelled request. + reason (Optional[str]): An optional reason for cancellation. + """ + + method: Literal["notifications/cancelled"] + request_id: Union[str, int] + reason: Optional[str] = None + + +class ProgressNotification(BaseModel): + """Notification of operation progress. + + Attributes: + method (Literal["notifications/progress"]): The notification method. + progress_token (ProgressToken): The token associated with the progress. + progress (float): The current progress value. + total (Optional[float]): The total progress value, if known. + """ + + method: Literal["notifications/progress"] + progress_token: ProgressToken + progress: float + total: Optional[float] = None + + +class LoggingNotification(BaseModel): + """Notification of log messages. + + Attributes: + method (Literal["notifications/message"]): The notification method. + level (LogLevel): The log level of the message. + logger (Optional[str]): The logger name. + data (Any): The log message data. + """ + + method: Literal["notifications/message"] + level: LogLevel + logger: Optional[str] = None + data: Any + + +# Federation types +class FederatedTool(Tool): + """A tool from a federated gateway. + + Attributes: + gateway_id (str): The identifier of the gateway. + gateway_name (str): The name of the gateway. + """ + + gateway_id: str + gateway_name: str + + +class FederatedResource(Resource): + """A resource from a federated gateway. + + Attributes: + gateway_id (str): The identifier of the gateway. + gateway_name (str): The name of the gateway. + """ + + gateway_id: str + gateway_name: str + + +class FederatedPrompt(Prompt): + """A prompt from a federated gateway. + + Attributes: + gateway_id (str): The identifier of the gateway. + gateway_name (str): The name of the gateway. + """ + + gateway_id: str + gateway_name: str + + +class Gateway(BaseModel): + """A federated gateway peer. + + Attributes: + id (str): The unique identifier for the gateway. + name (str): The name of the gateway. + url (AnyHttpUrl): The URL of the gateway. + capabilities (ServerCapabilities): The capabilities of the gateway. + last_seen (Optional[datetime]): Timestamp when the gateway was last seen. + """ + + id: str + name: str + url: AnyHttpUrl + capabilities: ServerCapabilities + last_seen: Optional[datetime] = None diff --git a/mcpgateway/utils/create_jwt_token.py b/mcpgateway/utils/create_jwt_token.py new file mode 100644 index 0000000000..edeaf65048 --- /dev/null +++ b/mcpgateway/utils/create_jwt_token.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""jwt_cli.py – generate, inspect, **and be imported** for token helpers. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +* **Run as a script** – friendly CLI (works with *no* flags). +* **Import as a library** – drop‑in async functions `create_jwt_token` & `get_jwt_token` + kept for backward‑compatibility, now delegating to the shared core helper. + +Quick usage +----------- +CLI (default secret, default payload): + $ python jwt_cli.py + +Library (unchanged API): +```python +from mcpgateway.utils.create_jwt_token import create_jwt_token, get_jwt_token + +# inside async context +jwt = await create_jwt_token({"username": "alice"}) +``` +""" + +from __future__ import annotations + +import argparse +import asyncio +import datetime as _dt +import json +import sys +from typing import Any, Dict, List, Sequence + +import jwt # PyJWT + +from mcpgateway.config import settings + +__all__: Sequence[str] = ( + "create_jwt_token", + "get_jwt_token", + "_create_jwt_token", +) + +# --------------------------------------------------------------------------- +# Defaults & constants +# --------------------------------------------------------------------------- +DEFAULT_SECRET: str = settings.jwt_secret_key +DEFAULT_ALGO: str = settings.jwt_algorithm +DEFAULT_EXP_MINUTES: int = settings.token_expiry # 7 days (in minutes) +DEFAULT_USERNAME: str = settings.basic_auth_user + + +# --------------------------------------------------------------------------- +# Core sync helper (used by both CLI & async wrappers) +# --------------------------------------------------------------------------- + + +def _create_jwt_token( + data: Dict[str, Any], + expires_in_minutes: int = DEFAULT_EXP_MINUTES, + secret: str = DEFAULT_SECRET, + algorithm: str = DEFAULT_ALGO, +) -> str: + """Return a signed JWT string (synchronous, timezone‑aware). + + Args: + data: Dictionary containing payload data to encode in the token. + expires_in_minutes: Token expiration time in minutes. Default is 7 days. + Set to 0 to disable expiration. + secret: Secret key used for signing the token. + algorithm: Signing algorithm to use. + + Returns: + The JWT token string. + """ + payload = data.copy() + if expires_in_minutes > 0: + expire = _dt.datetime.now(_dt.timezone.utc) + _dt.timedelta(minutes=expires_in_minutes) + payload["exp"] = int(expire.timestamp()) + return jwt.encode(payload, secret, algorithm=algorithm) + + +# --------------------------------------------------------------------------- +# **Async** wrappers for backward compatibility +# --------------------------------------------------------------------------- + + +async def create_jwt_token( + data: Dict[str, Any], + expires_in_minutes: int = DEFAULT_EXP_MINUTES, + *, + secret: str = DEFAULT_SECRET, + algorithm: str = DEFAULT_ALGO, +) -> str: + """Async facade for historic code. Internally synchronous—almost instant. + + Args: + data: Dictionary containing payload data to encode in the token. + expires_in_minutes: Token expiration time in minutes. Default is 7 days. + Set to 0 to disable expiration. + secret: Secret key used for signing the token. + algorithm: Signing algorithm to use. + + Returns: + The JWT token string. + """ + return _create_jwt_token(data, expires_in_minutes, secret, algorithm) + + +async def get_jwt_token() -> str: + """Return a token for ``{"username": "admin"}``, mirroring old behaviour. + + Returns: + The JWT token string with default admin username. + """ + user_data = {"username": DEFAULT_USERNAME} + return await create_jwt_token(user_data) + + +# --------------------------------------------------------------------------- +# **Decode** helper (non‑verifying) – used by the CLI +# --------------------------------------------------------------------------- + + +def _decode_jwt_token(token: str, algorithms: List[str] | None = None) -> Dict[str, Any]: + """Decode *without* signature verification—handy for inspection. + + Args: + token: JWT token string to decode. + algorithms: List of allowed algorithms for decoding. Defaults to [DEFAULT_ALGO]. + + Returns: + Dictionary containing the decoded payload. + """ + return jwt.decode( + token, + settings.jwt_secret_key, + algorithms=algorithms or [DEFAULT_ALGO], + # options={"require": ["exp"]}, # Require expiration + ) + + +# --------------------------------------------------------------------------- +# CLI Parsing & helpers +# --------------------------------------------------------------------------- + + +def _parse_args(): + p = argparse.ArgumentParser( + description="Generate or inspect JSON Web Tokens.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + group = p.add_mutually_exclusive_group() + group.add_argument("-u", "--username", help="Add username= to the payload.") + group.add_argument("-d", "--data", help="Raw JSON payload or comma‑separated key=value pairs.") + group.add_argument("--decode", metavar="TOKEN", help="Token string to decode (no verification).") + + p.add_argument( + "-e", + "--exp", + type=int, + default=DEFAULT_EXP_MINUTES, + help="Expiration in minutes (0 disables the exp claim).", + ) + p.add_argument("-s", "--secret", default=DEFAULT_SECRET, help="Secret key for signing.") + p.add_argument("--algo", default=DEFAULT_ALGO, help="Signing algorithm to use.") + p.add_argument("--pretty", action="store_true", help="Pretty‑print payload before encoding.") + + return p.parse_args() + + +def _payload_from_cli(args) -> Dict[str, Any]: + if args.username is not None: + return {"username": args.username} + + if args.data is not None: + # Attempt JSON first + try: + return json.loads(args.data) + except json.JSONDecodeError: + pairs = [kv.strip() for kv in args.data.split(",") if kv.strip()] + payload: Dict[str, Any] = {} + for pair in pairs: + if "=" not in pair: + raise ValueError(f"Invalid key=value pair: '{pair}'") + k, v = pair.split("=", 1) + payload[k.strip()] = v.strip() + return payload + + # Fallback default payload + return {"username": DEFAULT_USERNAME} + + +# --------------------------------------------------------------------------- +# Entry point for ``python jwt_cli.py`` +# --------------------------------------------------------------------------- + + +def main() -> None: # pragma: no cover + args = _parse_args() + + # Decode mode takes precedence + if args.decode: + decoded = _decode_jwt_token(args.decode, algorithms=[args.algo]) + json.dump(decoded, sys.stdout, indent=2, default=str) + sys.stdout.write("\n") + return + + payload = _payload_from_cli(args) + + if args.pretty: + print("Payload:") + print(json.dumps(payload, indent=2, default=str)) + print("-") + + token = _create_jwt_token(payload, args.exp, args.secret, args.algo) + print(token) + + +if __name__ == "__main__": + # Support being run via ``python -m mcpgateway.utils.create_jwt_token`` too + try: + # Respect existing asyncio loop if present (e.g. inside uvicorn dev server) + loop = asyncio.get_running_loop() + loop.run_until_complete(asyncio.sleep(0)) # no‑op to ensure loop alive + except RuntimeError: + # No loop; we're just a simple CLI call – run main synchronously + main() + else: + # We're inside an active asyncio program – delegate to executor to avoid blocking + loop.run_in_executor(None, main) diff --git a/mcpgateway/utils/services_auth.py b/mcpgateway/utils/services_auth.py new file mode 100644 index 0000000000..9b75d05fb2 --- /dev/null +++ b/mcpgateway/utils/services_auth.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" + +import base64 +import hashlib +import json +import os + +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +from mcpgateway.config import settings + + +def get_key() -> bytes: + """ + Generate a 32-byte AES encryption key derived from a passphrase. + + Returns: + bytes: A 32-byte encryption key. + + Raises: + ValueError: If the passphrase is not set or empty. + """ + passphrase = settings.auth_encryption_secret + if not passphrase: + raise ValueError("AUTH_ENCRPYPTION_SECRET not set in environment.") + return hashlib.sha256(passphrase.encode()).digest() # 32-byte key + + +def encode_auth(auth_value: dict) -> str: + """ + Encrypt and encode an authentication dictionary into a compact base64-url string. + + Args: + auth_value (dict): The authentication dictionary to encrypt and encode. + + Returns: + str: A base64-url-safe encrypted string representing the dictionary, or None if input is None. + """ + if auth_value is None: + return None + plaintext = json.dumps(auth_value) + key = get_key() + aesgcm = AESGCM(key) + nonce = os.urandom(12) + ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None) + combined = nonce + ciphertext + encoded = base64.urlsafe_b64encode(combined).rstrip(b"=") + return encoded.decode() + + +def decode_auth(encoded_value: str) -> dict: + """ + Decode and decrypt a base64-url-safe encrypted string back into the authentication dictionary. + + Args: + encoded_value (str): The encrypted base64-url string to decode and decrypt. + + Returns: + dict: The decrypted authentication dictionary, or empty dict if input is None. + """ + if encoded_value is None: + return {} + key = get_key() + aesgcm = AESGCM(key) + # Fix base64 padding + padded = encoded_value + "=" * (-len(encoded_value) % 4) + combined = base64.urlsafe_b64decode(padded) + nonce = combined[:12] + ciphertext = combined[12:] + plaintext = aesgcm.decrypt(nonce, ciphertext, None) + return json.loads(plaintext.decode()) diff --git a/mcpgateway/utils/verify_credentials.py b/mcpgateway/utils/verify_credentials.py new file mode 100644 index 0000000000..efda9a2bdc --- /dev/null +++ b/mcpgateway/utils/verify_credentials.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" + +from typing import Optional + +import jwt +from fastapi import Cookie, Depends, HTTPException, status +from fastapi.security import ( + HTTPAuthorizationCredentials, + HTTPBasic, + HTTPBasicCredentials, + HTTPBearer, +) +from fastapi.security.utils import get_authorization_scheme_param +from jwt import PyJWTError + +from mcpgateway.config import settings + +basic_security = HTTPBasic(auto_error=False) +security = HTTPBearer(auto_error=False) + + +async def verify_jwt_token(token: str) -> dict: + """Verify and decode a JWT token. + + Args: + token: The JWT token to verify. + + Returns: + dict: The decoded token payload containing claims. + + Raises: + HTTPException: If the token has expired or is invalid. + """ + try: + # Decode and validate token + payload = jwt.decode( + token, + settings.jwt_secret_key, + algorithms=[settings.jwt_algorithm], + # options={"require": ["exp"]}, # Require expiration + ) + return payload # Contains the claims (e.g., user info) + except jwt.ExpiredSignatureError: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Token has expired", + headers={"WWW-Authenticate": "Bearer"}, + ) + except PyJWTError: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid token", + headers={"WWW-Authenticate": "Bearer"}, + ) + + +async def verify_credentials(token: str) -> dict: + """Verify credentials using a JWT token. + + This function uses verify_jwt_token internally which may raise exceptions. + + Args: + token: The JWT token to verify. + + Returns: + dict: The validated token payload with the original token added. + """ + payload = await verify_jwt_token(token) + payload["token"] = token + return payload + + +async def require_auth(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), jwt_token: Optional[str] = Cookie(None)) -> str | dict: + """Require authentication via JWT token. + + Checks for a JWT token either in the Authorization header or as a cookie. + + Args: + credentials: HTTP Authorization credentials from the request header. + jwt_token: JWT token from cookies. + + Returns: + str or dict: The verified credentials payload or "anonymous" if authentication is not required. + + Raises: + HTTPException: If authentication is required but no valid token is provided. + """ + token = credentials.credentials if credentials else jwt_token + + if settings.auth_required and not token: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, + ) + return await verify_credentials(token) if token else "anonymous" + + +async def verify_basic_credentials(credentials: HTTPBasicCredentials) -> str: + """Verify provided credentials. + + Args: + credentials: HTTP Basic credentials. + + Returns: + The username if credentials are valid. + + Raises: + HTTPException: If credentials are invalid. + """ + is_valid_user = credentials.username == settings.basic_auth_user + is_valid_pass = credentials.password == settings.basic_auth_password + + if not (is_valid_user and is_valid_pass): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid credentials", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username + + +async def require_basic_auth(credentials: HTTPBasicCredentials = Depends(basic_security)) -> str: + """Require valid authentication. + + Args: + credentials: HTTP Basic credentials provided by the client. + + Returns: + str: The authenticated username or "anonymous" if auth is not required. + + Raises: + HTTPException: If authentication is required but no valid credentials are provided. + """ + if settings.auth_required: + if not credentials: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Basic"}, + ) + return await verify_basic_credentials(credentials) + return "anonymous" + + +async def require_auth_override( + auth_header: str | None = None, + jwt_token: str | None = None, +) -> str | dict: + """ + Call :func:`require_auth` manually from middleware, without FastAPI + dependency injection. + + Args: + auth_header: Raw ``Authorization`` header value + (e.g. ``"Bearer eyJhbGciOi..."``). + jwt_token: JWT taken from a cookie. If both header and cookie are + supplied, the header wins. + + Returns: + str or dict: Whatever :func:`require_auth` returns + (decoded JWT payload or the string ``"anonymous"``). + + Note: + This wrapper may propagate :class:`fastapi.HTTPException` raised by + :func:`require_auth`, but it does not raise anything on its own, so + we omit a formal *Raises* section to satisfy pydocstyle. + """ + credentials = None + if auth_header: + scheme, param = get_authorization_scheme_param(auth_header) + if scheme.lower() == "bearer" and param: + credentials = HTTPAuthorizationCredentials(scheme=scheme, credentials=param) + + return await require_auth(credentials=credentials, jwt_token=jwt_token) diff --git a/mcpgateway/validation/__init__.py b/mcpgateway/validation/__init__.py new file mode 100644 index 0000000000..25aba61ee3 --- /dev/null +++ b/mcpgateway/validation/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +"""Validation Package. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Provides validation components for the MCP Gateway including: +- JSON-RPC request/response validation +""" + +from mcpgateway.validation.jsonrpc import ( + JSONRPCError, + validate_request, + validate_response, +) + +__all__ = ["validate_request", "validate_response", "JSONRPCError"] diff --git a/mcpgateway/validation/jsonrpc.py b/mcpgateway/validation/jsonrpc.py new file mode 100644 index 0000000000..9f9fffdebd --- /dev/null +++ b/mcpgateway/validation/jsonrpc.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +"""JSON-RPC Validation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module provides validation functions for JSON-RPC 2.0 requests and responses +according to the specification at https://www.jsonrpc.org/specification. + +Includes: +- Request validation +- Response validation +- Standard error codes +- Error message formatting +""" + +from typing import Any, Dict, Optional, Union + + +class JSONRPCError(Exception): + """JSON-RPC protocol error.""" + + def __init__( + self, + code: int, + message: str, + data: Optional[Any] = None, + request_id: Optional[Union[str, int]] = None, + ): + """Initialize JSON-RPC error. + + Args: + code: Error code + message: Error message + data: Optional error data + request_id: Optional request ID + """ + self.code = code + self.message = message + self.data = data + self.request_id = request_id + super().__init__(message) + + def to_dict(self) -> Dict[str, Any]: + """Convert error to JSON-RPC error response dict. + + Returns: + Error response dictionary + """ + error = {"code": self.code, "message": self.message} + if self.data is not None: + error["data"] = self.data + + return {"jsonrpc": "2.0", "error": error, "request_id": self.request_id} + + +# Standard JSON-RPC error codes +PARSE_ERROR = -32700 # Invalid JSON +INVALID_REQUEST = -32600 # Invalid Request object +METHOD_NOT_FOUND = -32601 # Method not found +INVALID_PARAMS = -32602 # Invalid method parameters +INTERNAL_ERROR = -32603 # Internal JSON-RPC error +SERVER_ERROR_START = -32000 # Start of server error codes +SERVER_ERROR_END = -32099 # End of server error codes + + +def validate_request(request: Dict[str, Any]) -> None: + """Validate JSON-RPC request. + + Args: + request: Request dictionary to validate + + Raises: + JSONRPCError: If request is invalid + """ + # Check jsonrpc version + if request.get("jsonrpc") != "2.0": + raise JSONRPCError(INVALID_REQUEST, "Invalid JSON-RPC version", request_id=request.get("id")) + + # Check method + method = request.get("method") + if not isinstance(method, str) or not method: + raise JSONRPCError(INVALID_REQUEST, "Invalid or missing method", request_id=request.get("id")) + + # Check ID for requests (not notifications) + if "id" in request: + request_id = request["id"] + if not isinstance(request_id, (str, int)) or isinstance(request_id, bool): + raise JSONRPCError(INVALID_REQUEST, "Invalid request ID type", request_id=None) + + # Check params if present + params = request.get("params") + if params is not None: + if not isinstance(params, (dict, list)): + raise JSONRPCError(INVALID_REQUEST, "Invalid params type", request_id=request.get("id")) + + +def validate_response(response: Dict[str, Any]) -> None: + """Validate JSON-RPC response. + + Args: + response: Response dictionary to validate + + Raises: + JSONRPCError: If response is invalid + """ + # Check jsonrpc version + if response.get("jsonrpc") != "2.0": + raise JSONRPCError(INVALID_REQUEST, "Invalid JSON-RPC version", request_id=response.get("id")) + + # Check ID + if "id" not in response: + raise JSONRPCError(INVALID_REQUEST, "Missing response ID", request_id=None) + + response_id = response["id"] + if not isinstance(response_id, (str, int, type(None))) or isinstance(response_id, bool): + raise JSONRPCError(INVALID_REQUEST, "Invalid response ID type", request_id=None) + + # Check result XOR error + has_result = "result" in response + has_error = "error" in response + + if not has_result and not has_error: + raise JSONRPCError(INVALID_REQUEST, "Response must contain either result or error", request_id=id) + if has_result and has_error: + raise JSONRPCError(INVALID_REQUEST, "Response cannot contain both result and error", request_id=id) + + # Validate error object + if has_error: + error = response["error"] + if not isinstance(error, dict): + raise JSONRPCError(INVALID_REQUEST, "Invalid error object type", request_id=id) + + if "code" not in error or "message" not in error: + raise JSONRPCError(INVALID_REQUEST, "Error must contain code and message", request_id=id) + + if not isinstance(error["code"], int): + raise JSONRPCError(INVALID_REQUEST, "Error code must be integer", request_id=id) + + if not isinstance(error["message"], str): + raise JSONRPCError(INVALID_REQUEST, "Error message must be string", request_id=id) diff --git a/os_deps.sh b/os_deps.sh new file mode 100755 index 0000000000..4c4a0a9f2d --- /dev/null +++ b/os_deps.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# +# scripts/os_deps.sh – install/verify Graphviz, Pandoc, Trivy, SCC +# Supports macOS (Intel/Apple-Silicon), Debian/Ubuntu, RHEL/Fedora, +# generic Linux, Windows (prints manual steps). +# ────────────────────────────────────────────────────────────────────────── + +set -euo pipefail + +# ───────────────────────── helpers ───────────────────────── +have() { command -v "$1" >/dev/null 2>&1; } +need() { ! have "$1"; } # true if command missing +sudo_if() { [ "$(id -u)" -ne 0 ] && echo "sudo"; } + +install_scc() { + local ver="v3.5.0" os="$1" arch="$2" file url tmp + file="scc_${os}_${arch}" + [[ "$os" == "Windows" ]] && file="${file}.zip" || file="${file}.tar.gz" + url="https://github.com/boyter/scc/releases/download/${ver}/${file}" + + echo "⬇️ Installing scc from $url" + tmp=$(mktemp -d) + curl -sSL "$url" -o "$tmp/scc_pkg" + if [[ "$file" == *.zip ]]; then unzip -q "$tmp/scc_pkg" -d "$tmp" + else tar -xf "$tmp/scc_pkg" -C "$tmp"; fi + $(sudo_if) mv "$tmp/scc" /usr/local/bin/ + $(sudo_if) chmod +x /usr/local/bin/scc + rm -rf "$tmp" + echo "✅ scc installed." +} + +install_trivy_deb() { + echo "📦 Setting up Aqua Security Trivy APT repo…" + $(sudo_if) apt-get update -qq + $(sudo_if) apt-get install -y wget gnupg lsb-release + wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \ + | gpg --dearmor \ + | $(sudo_if) tee /usr/share/keyrings/trivy.gpg >/dev/null + echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb \ +generic main" \ + | $(sudo_if) tee /etc/apt/sources.list.d/trivy.list >/dev/null + $(sudo_if) apt-get update -qq + $(sudo_if) apt-get install -y trivy +} + +install_trivy_rpm() { + echo "📦 Setting up Aqua Security Trivy YUM/DNF repo…" + cat << 'EOF' | $(sudo_if) tee /etc/yum.repos.d/trivy.repo >/dev/null +[trivy] +name=Trivy repository +baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$basearch/ +gpgcheck=1 +enabled=1 +gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key +EOF + if have dnf; then + $(sudo_if) dnf -y update + $(sudo_if) dnf -y install trivy + else + $(sudo_if) yum -y update + $(sudo_if) yum -y install trivy + fi +} + +# ───────────────────────── main logic ───────────────────────── +echo "🔍 Checking platform prerequisites …" +MISSING=() +for cmd in dot pandoc trivy scc; do + need "$cmd" && MISSING+=("$cmd") +done + +[[ ${#MISSING[@]} -eq 0 ]] && { echo "✅ All commands present."; exit 0; } +echo "⚠️ Missing: ${MISSING[*]}" + +OS=$(uname -s) +ARCH=$(uname -m) +case "$ARCH" in + aarch64|arm64) ARCH_TAG="arm64" ;; + x86_64|amd64) ARCH_TAG="x86_64" ;; + i*86) ARCH_TAG="i386" ;; + *) ARCH_TAG="$ARCH" ;; +esac +echo "🏷️ Detected $OS / $ARCH_TAG" + +# ───────────────────────── per-OS actions ───────────────────────── +if [[ "$OS" == "Darwin" ]]; then + have brew || { echo "❌ Homebrew missing → https://brew.sh"; exit 1; } + need dot && brew install graphviz + need pandoc && brew install pandoc + need trivy && brew install aquasecurity/trivy/trivy + need scc && install_scc "Darwin" "$ARCH_TAG" + +elif [[ "$OS" == "Linux" ]]; then + if have apt-get; then + PKG_MGR="apt-get" + INSTALL="$(sudo_if) apt-get install -y --no-install-recommends" + $(sudo_if) apt-get update -qq + need graphviz && $INSTALL graphviz + need pandoc && $INSTALL pandoc + if need trivy; then install_trivy_deb; fi + elif have dnf || have yum; then + PKG_MGR=$(have dnf && echo dnf || echo yum) + INSTALL="$(sudo_if) $PKG_MGR install -y" + $(sudo_if) $PKG_MGR -y update || true + need graphviz && $INSTALL graphviz + need pandoc && $INSTALL pandoc + if need trivy; then install_trivy_rpm; fi + else + echo "❌ Unsupported Linux package manager – please install Graphviz, Pandoc, and Trivy manually." + fi + need scc && install_scc "Linux" "$ARCH_TAG" + +elif [[ "$OS" =~ MINGW|MSYS|CYGWIN ]]; then + echo "ℹ️ On Windows please install:" + echo " • Graphviz (https://graphviz.org)" + echo " • Pandoc (https://pandoc.org/installing.html)" + echo " • Trivy (https://aquasecurity.github.io/trivy/)" + need scc && install_scc "Windows" "$ARCH_TAG" + +else + echo "❌ Unsupported OS – manual install required." + exit 1 +fi + +echo "🎉 Done." diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..81b5e03c8b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1498 @@ +{ + "name": "mcp-context-forge", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "stylelint-order": "^7.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "dev": true, + "peer": true, + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "peer": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cacheable": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz", + "integrity": "sha512-8D5htMCxPDUULux9gFzv30f04Xo3wCnik0oOxKoRTPIBoqA7HtOcJ87uBhQTs3jCfZZTrUBGsYIZOgE0ZRgMAg==", + "dev": true, + "peer": true, + "dependencies": { + "hookified": "^1.8.2", + "keyv": "^5.3.3" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz", + "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==", + "dev": true, + "peer": true, + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "peer": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "peer": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/css-functions-list": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12 || >=16" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "peer": true, + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "peer": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "peer": true + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "peer": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "peer": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "peer": true + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "peer": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "peer": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "peer": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hookified": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.9.0.tgz", + "integrity": "sha512-2yEEGqphImtKIe1NXWEhu6yD3hlFR4Mxk4Mtp3XEyScpSt4pQ4ymmXA1zzxZpj99QkFK+nN0nzjeb2+RUi/6CQ==", + "dev": true, + "peer": true + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "peer": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", + "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", + "dev": true, + "peer": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "peer": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "peer": true + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "peer": true + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "peer": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "peer": true + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sorting": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz", + "integrity": "sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==", + "dev": true, + "peerDependencies": { + "postcss": "^8.4.20" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "peer": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "peer": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint": { + "version": "16.19.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.19.1.tgz", + "integrity": "sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "peer": true, + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.1.0", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^10.0.8", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.3", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.36.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.3", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-order": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-7.0.0.tgz", + "integrity": "sha512-rSWxx0KscYfxU02wEskKXES9lkRzuuONMMNkZ7SUc6uiF3tDKm7e+sE0Ax/SBlG4TUf1sp1R6f3/SlsPGmzthg==", + "dev": true, + "dependencies": { + "postcss": "^8.5.3", + "postcss-sorting": "^9.1.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^16.18.0" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, + "peer": true + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.0.tgz", + "integrity": "sha512-Et/ex6smi3wOOB+n5mek+Grf7P2AxZR5ueqRUvAAn4qkyatXi3cUC1cuQXVkX0VlzBVsN4BkWJFmY/fYiRTdww==", + "dev": true, + "peer": true, + "dependencies": { + "flat-cache": "^6.1.9" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.9.tgz", + "integrity": "sha512-DUqiKkTlAfhtl7g78IuwqYM+YqvT+as0mY+EVk6mfimy19U79pJCzDZQsnqk3Ou/T6hFXWLGbwbADzD/c8Tydg==", + "dev": true, + "peer": true, + "dependencies": { + "cacheable": "^1.9.0", + "flatted": "^3.3.3", + "hookified": "^1.8.2" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true, + "peer": true + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "peer": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "peer": true + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..e6699436c3 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "stylelint-order": "^7.0.0" + } +} diff --git a/podman-compose-sonarqube.yaml b/podman-compose-sonarqube.yaml new file mode 100644 index 0000000000..0165c94019 --- /dev/null +++ b/podman-compose-sonarqube.yaml @@ -0,0 +1,39 @@ +version: "2" + +services: + sonarqube: + image: docker.io/library/sonarqube:latest + ports: + - "9000:9000" + networks: + - sonarnet + environment: + - SONARQUBE_JDBC_URL=jdbc:postgresql://db:5432/sonar + volumes: + - sonarqube_conf:/opt/sonarqube/conf + - sonarqube_data:/opt/sonarqube/data + - sonarqube_extensions:/opt/sonarqube/extensions + - sonarqube_bundled-plugins:/opt/sonarqube/lib/bundled-plugins + + db: + image: docker.io/library/postgres + networks: + - sonarnet + environment: + - POSTGRES_USER=sonar + - POSTGRES_PASSWORD=sonar + volumes: + - postgresql:/var/lib/postgresql + - postgresql_data:/var/lib/postgresql/data + +networks: + sonarnet: + driver: bridge + +volumes: + sonarqube_conf: + sonarqube_data: + sonarqube_extensions: + sonarqube_bundled-plugins: + postgresql: + postgresql_data: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..338eb10190 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,193 @@ +[build-system] +requires = [ "setuptools>=42", "wheel",] +build-backend = "setuptools.build_meta" + +[project] +name = "mcpgateway" +version = "1.0.0" +description = "A production-ready MCP Gateway built with FastAPI and the official MCP Python SDK" +keywords = ["MCP", "Gateway", "API", "Agents", "Tools"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Framework :: FastAPI", +] +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "cryptography>=45.0.3", + "fastapi>=0.115.12", + "gunicorn>=23.0.0", + "httpx>=0.28.1", + "jinja2>=3.1.6", + "jq>=1.8.0", + "jsonpath-ng>=1.7.0", + "jsonschema>=4.23.0", + "mcp>=1.9.1", + "parse>=1.20.2", + "pydantic>=2.11.5", + "pydantic-settings>=2.9.1", + "pyjwt>=2.10.1", + "sqlalchemy>=2.0.41", + "sse-starlette>=2.3.5", + "starlette>=0.46.2", + "uvicorn>=0.34.2", + "zeroconf>=0.147.0", +] +[[project.authors]] +name = "Mihai Criveti" +email = "redacted@ibm.com" + +[project.license] +text = "Apache License 2.0" + +[project.optional-dependencies] +dev = [ + "argparse-manpage>=4.6", + "autoflake>=2.3.1", + "bandit>=1.8.3", + "black>=25.1.0", + "bump2version>=1.0.1", + "check-manifest>=0.50", + "code2flow>=2.5.1", + "cookiecutter>=2.6.0", + "coverage>=7.8.2", + "coverage-badge>=1.1.2", + "darglint>=1.8.1", + "fawltydeps>=0.19.0", + "flake8>=7.2.0", + "gprof2dot>=2025.4.14", + "importchecker>=3.0", + "isort>=6.0.1", + "mypy>=1.15.0", + "pexpect>=4.9.0", + "pip-licenses>=5.0.0", + "pip_audit>=2.9.0", + "pre-commit>=4.2.0", + "pydocstyle>=6.3.0", + "pylint>=3.3.7", + "pylint-pydantic>=0.3.5", + "pyre-check>=0.9.23", + "pyright>=1.1.401", + "pyroma>=4.2", + "pyspelling>=2.10", + "pytest>=8.3.5", + "pytest-asyncio>=1.0.0", + "pytest-cov>=6.1.1", + "pytest-examples>=0.0.18", + "pytest-md-report>=0.7.0", + "pytest-rerunfailures>=15.1", + "pytest-xdist>=3.6.1", + "pytype>=2024.10.11", + "pytype>=2024.10.11", + "radon>=6.0.1", + "ruff>=0.11.11", + "settings-doc>=4.3.2", + "snakeviz>=2.2.2", + "ty>=0.0.1a6", + "types-tabulate>=0.9.0.20241207", +] +redis = [ + "redis>=6.1.0", +] + +[project.urls] +Homepage = "https://github.com/IBM/mcp-context-forge" +Documentation = "https://github.com/IBM/mcp-context-forge/docs" +Source = "https://github.com/IBM/mcp-context-forge" + +[tool.black] +line-length = 200 +target-version = [ "py310",] +include = "\\.pyi?$" + +[tool.isort] +profile = "black" +multi_line_output = 3 + +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-ra -q --cov=mcpgateway" +testpaths = [ "tests",] +asyncio_mode = "auto" + +[tool.setuptools.packages.find] +where = [ ".",] +include = [ "mcpgateway*",] + +[tool.pytype] +# Directory-specific options: +inputs = ["mcpgateway"] +python_version = "3.11" # match your default runtime + +[tool.check-manifest] +# Usually you only need 'ignore', but other flags are available. +ignore = [ + "docs/**", + "tests/**", + ".github/**", + "Makefile", +] + +[tool.fawltydeps] +# only parse your main pyproject.toml +deps = ["pyproject.toml"] + +# ignore all of your 'dev' extras so they won't show up +ignore_unused = [ + "autoflake", + "argparse-manpage", + "bandit", + "black", + "bump2version", + "check-manifest", + "code2flow", + "cookiecutter", + "coverage", + "coverage-badge", + "darglint", + "flake8", + "fawltydeps", + "gprof2dot", + "gunicorn", + "importchecker", + "isort", + "ty", + "mypy", + "pexpect", + "pip-licenses", + "pip_audit", + "pre-commit", + "pydocstyle", + "pylint", + "pylint-pydantic", + "pyre-check", + "pyright", + "pyroma", + "pyspelling", + "pytest", + "pytest-asyncio", + "pytest-cov", + "pytest-examples", + "pytest-md-report", + "pytest-rerunfailures", + "pytest-xdist", + "pytype", + "radon", + "ruff", + "settings-doc", + "snakeviz", + "types-tabulate", + "uvicorn" +] diff --git a/run-gunicorn.sh b/run-gunicorn.sh new file mode 100755 index 0000000000..ec529aec3d --- /dev/null +++ b/run-gunicorn.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Author: Mihai Criveti +# Description: Run Gunicorn production server (optionally with TLS) + +cat << "EOF" +███╗ ███╗ ██████╗██████╗ ██████╗ █████╗ ████████╗███████╗██╗ ██╗ █████╗ ██╗ ██╗ +████╗ ████║██╔════╝██╔══██╗ ██╔════╝ ██╔══██╗╚══██╔══╝██╔════╝██║ ██║██╔══██╗╚██╗ ██╔╝ +██╔████╔██║██║ ██████╔╝ ██║ ███╗███████║ ██║ █████╗ ██║ █╗ ██║███████║ ╚████╔╝ +██║╚██╔╝██║██║ ██╔═══╝ ██║ ██║██╔══██║ ██║ ██╔══╝ ██║███╗██║██╔══██║ ╚██╔╝ +██║ ╚═╝ ██║╚██████╗██║ ╚██████╔╝██║ ██║ ██║ ███████╗╚███╔███╔╝██║ ██║ ██║ +╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ +EOF + +# ────────────────────────────── +# Tunables (env-overrideable) +# ────────────────────────────── +GUNICORN_WORKERS=${GUNICORN_WORKERS:-8} +GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-600} +GUNICORN_MAX_REQUESTS=${GUNICORN_MAX_REQUESTS:-1000} +GUNICORN_MAX_REQUESTS_JITTER=${GUNICORN_MAX_REQUESTS_JITTER:-100} + +# TLS options +SSL=${SSL:-false} # true|false +CERT_FILE=${CERT_FILE:-certs/cert.pem} # path to cert +KEY_FILE=${KEY_FILE:-certs/key.pem} # path to key + +SSL_ARGS="" +if [[ "${SSL}" == "true" ]]; then + if [[ ! -f "${CERT_FILE}" || ! -f "${KEY_FILE}" ]]; then + echo "✘ SSL requested but certificate files not found:" + echo " CERT_FILE=${CERT_FILE}" + echo " KEY_FILE=${KEY_FILE}" + exit 1 + fi + SSL_ARGS="--certfile=${CERT_FILE} --keyfile=${KEY_FILE}" + echo "✓ TLS enabled – using ${CERT_FILE} / ${KEY_FILE}" +fi + +exec gunicorn -c gunicorn.config.py \ + --worker-class uvicorn.workers.UvicornWorker \ + --workers "${GUNICORN_WORKERS}" \ + --timeout "${GUNICORN_TIMEOUT}" \ + --max-requests "${GUNICORN_MAX_REQUESTS}" \ + --max-requests-jitter "${GUNICORN_MAX_REQUESTS_JITTER}" \ + --access-logfile - \ + --error-logfile - \ + ${SSL_ARGS} \ + "mcpgateway.main:app" diff --git a/run.sh b/run.sh new file mode 100755 index 0000000000..50fc68ec11 --- /dev/null +++ b/run.sh @@ -0,0 +1,195 @@ +#!/bin/bash + +# Author: Mihai Criveti + +# Exit on any error +set -e + +# Default settings +HOST=${HOST:-0.0.0.0} +PORT=${PORT:-4444} +ENV_FILE=${ENV_FILE:-.env} +LOG_LEVEL=${LOG_LEVEL:-info} +LOG_FORMAT=${LOG_FORMAT:-json} +RELOAD=${RELOAD:-false} +WORKERS=${WORKERS:-1} +ACCESS_LOG=${ACCESS_LOG:-true} +APP_MODULE=${APP_MODULE:-mcpgateway.main:app} + +# Help message +show_help() { + echo "MCP Gateway Runner" + echo "===================" + echo "" + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -e, --env FILE Use specific env file (default: .env)" + echo " -H, --host HOST Bind to HOST address (default: 0.0.0.0)" + echo " -p, --port PORT Use PORT (default: 4444)" + echo " -l, --log LEVEL Set log level: debug, info, warning, error, critical (default: info)" + echo " -f, --format FORMAT Set log format: json or text (default: json)" + echo " -r, --reload Enable auto-reload for development" + echo " -w, --workers N Number of worker processes (default: 1)" + echo " -n, --no-access-log Disable access log" + echo " -m, --module MODULE Use specified app module (default: mcpgateway.main:app)" + echo "" +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + exit 0 + ;; + -e|--env) + ENV_FILE="$2" + shift 2 + ;; + -H|--host) + HOST="$2" + shift 2 + ;; + -p|--port) + PORT="$2" + shift 2 + ;; + -l|--log) + LOG_LEVEL="$2" + shift 2 + ;; + -r|--reload) + RELOAD=true + shift + ;; + -w|--workers) + WORKERS="$2" + shift 2 + ;; + -n|--no-access-log) + ACCESS_LOG=false + shift + ;; + -m|--module) + APP_MODULE="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac +done + +# Validate log level +if ! [[ "$LOG_LEVEL" =~ ^(debug|info|warning|error|critical)$ ]]; then + echo "Invalid log level: $LOG_LEVEL" + echo "Must be one of: debug, info, warning, error, critical" + exit 1 +fi + +# Validate log format +if ! [[ "$LOG_FORMAT" =~ ^(json|text)$ ]]; then + echo "Invalid log format: $LOG_FORMAT" + echo "Must be one of: json, text" + exit 1 +fi + +# Create default .env if it doesn't exist +if [ ! -f "$ENV_FILE" ]; then + echo "Creating default $ENV_FILE..." + cat > "$ENV_FILE" << EOL +# Basic Settings +APP_NAME=MCP_Gateway +HOST=${HOST} +PORT=${PORT} +DATABASE_URL=sqlite:///./mcp.db +LOG_LEVEL=${LOG_LEVEL^^} +LOG_FORMAT=${LOG_FORMAT} + +# Authentication +BASIC_AUTH_USER=admin +BASIC_AUTH_PASSWORD=changeme +AUTH_REQUIRED=true +AUTH_ENCRYPTION_SECRET=my-test-salt + +# Security +SKIP_SSL_VERIFY=false +ALLOWED_ORIGINS='["http://localhost", "http://localhost:${PORT}"]' +CORS_ENABLED=true + +# Transport +TRANSPORT_TYPE=all +WEBSOCKET_PING_INTERVAL=30 +SSE_RETRY_TIMEOUT=5000 + +# Federation +FEDERATION_ENABLED=true +FEDERATION_DISCOVERY=false +FEDERATION_TIMEOUT=30 +FEDERATION_SYNC_INTERVAL=300 + +# Resources +RESOURCE_CACHE_SIZE=1000 +RESOURCE_CACHE_TTL=3600 +MAX_RESOURCE_SIZE=10485760 + +# Tools +TOOL_TIMEOUT=60 +MAX_TOOL_RETRIES=3 +TOOL_RATE_LIMIT=100 +TOOL_CONCURRENT_LIMIT=10 + +# Database +DB_POOL_SIZE=200 +DB_MAX_OVERFLOW=10 +DB_POOL_TIMEOUT=30 +DB_POOL_RECYCLE=3600 + +# Development +DEV_MODE=$([ "$RELOAD" = "true" ] && echo "true" || echo "false") +EOL +fi + +# Load environment variables +if [ -f "$ENV_FILE" ]; then + echo "Loading environment from $ENV_FILE..." + set -a + source "$ENV_FILE" + set +a +fi + +# Create database directory if needed +if [[ "$DATABASE_URL" == sqlite:* ]]; then + DB_PATH=$(echo "$DATABASE_URL" | sed -e 's/^sqlite:\/\///') + DB_DIR=$(dirname "$DB_PATH") + echo "Ensuring database directory exists: $DB_DIR" + mkdir -p "$DB_DIR" +fi + +# Build log configuration +if [ "$ACCESS_LOG" = "false" ]; then + LOG_CONFIG="$LOG_CONFIG --no-access-log" +fi + +# Check if running in development mode +if [ "$RELOAD" = "true" ]; then + echo "Starting MCP Gateway in development mode..." + echo "Running: uvicorn $APP_MODULE --host $HOST --port $PORT --reload $LOG_CONFIG" + exec uvicorn $APP_MODULE \ + --host "$HOST" \ + --port "$PORT" \ + --reload \ + $LOG_CONFIG +else + echo "Starting MCP Gateway in production mode..." + echo "Running: uvicorn $APP_MODULE --host $HOST --port $PORT --workers $WORKERS $LOG_CONFIG" + exec uvicorn $APP_MODULE \ + --host "$HOST" \ + --port "$PORT" \ + --workers "$WORKERS" \ + $LOG_CONFIG +fi diff --git a/sonar-code.properties b/sonar-code.properties new file mode 100644 index 0000000000..580545c7ba --- /dev/null +++ b/sonar-code.properties @@ -0,0 +1,7 @@ +sonar.projectKey=mcp-context-forge +sonar.projectName=mcp-context-forge +sonar.projectVersion=1.0 + +sonar.python.coverage.reportPaths=test/coverage.xml + +sonar.sources=./ diff --git a/test_readme.py b/test_readme.py new file mode 100644 index 0000000000..e5b8e2c7ae --- /dev/null +++ b/test_readme.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Test README examples. + +This module uses pytest-examples to test code examples from README.md. +""" + +import os +import sys +from pathlib import Path + +import pytest + +# Add the parent directory to the path so that we can import the module +sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__)))) + +# Load README examples +examples = [] +readme_path = Path(__file__).parent / "README.md" +if readme_path.exists(): + with open(readme_path, "r") as f: + readme = f.read() + + # Find and parse example code blocks + # TODO: Parse code blocks when they exist in README.md + # For now, this is a placeholder file + + +def test_readme_examples(): + """Test code examples in README.""" + # This is a placeholder test that always passes + # Later, actual code examples from README can be tested + assert True diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..c14ffa7340 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +import asyncio +import os +from typing import AsyncGenerator, Generator +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from fastapi import FastAPI +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, sessionmaker + +from mcpgateway.config import Settings, get_settings +from mcpgateway.db import Base + + +@pytest.fixture(scope="session") +def event_loop(): + """Create an instance of the default event loop for each test session.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest.fixture(scope="session") +def test_db_url(): + """Return the URL for the test database.""" + return "sqlite:///./test.db" + + +@pytest.fixture(scope="session") +def test_engine(test_db_url): + """Create a SQLAlchemy engine for testing.""" + engine = create_engine(test_db_url, connect_args={"check_same_thread": False}) + Base.metadata.create_all(bind=engine) + yield engine + Base.metadata.drop_all(bind=engine) + if os.path.exists("./test.db"): + os.remove("./test.db") + + +@pytest.fixture +def test_db(test_engine): + """Create a fresh database session for a test.""" + TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) + db = TestingSessionLocal() + try: + yield db + finally: + db.close() + + +@pytest.fixture +def test_settings(): + """Create test settings with in-memory database.""" + return Settings( + database_url="sqlite:///:memory:", + basic_auth_user="testuser", + basic_auth_password="testpass", + auth_required=False, + ) + + +@pytest.fixture +def app(test_settings): + """Create a FastAPI test application.""" + with patch("mcpgateway.config.get_settings", return_value=test_settings): + from mcpgateway.main import app + + yield app + + +@pytest.fixture +def mock_http_client(): + """Create a mock HTTP client.""" + mock = AsyncMock() + mock.aclose = AsyncMock() + return mock + + +@pytest.fixture +def mock_websocket(): + """Create a mock WebSocket.""" + mock = AsyncMock() + mock.accept = AsyncMock() + mock.send_json = AsyncMock() + mock.receive_json = AsyncMock() + mock.close = AsyncMock() + return mock diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/e2e/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/e2e/test_admin_workflow.py b/tests/e2e/test_admin_workflow.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/e2e/test_admin_workflow.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/e2e/test_client_workflow.py b/tests/e2e/test_client_workflow.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/e2e/test_client_workflow.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/e2e/test_federation_workflow.py b/tests/e2e/test_federation_workflow.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/e2e/test_federation_workflow.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/hey/hey.sh b/tests/hey/hey.sh new file mode 100755 index 0000000000..edb48d92c7 --- /dev/null +++ b/tests/hey/hey.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash + +# ============================================================================== +# A wrapper around the `hey` HTTP load testing tool. +# +# Features: +# - Strict mode (`set -euo pipefail`) for safer scripting +# - Dependency checks for required commands +# - Flexible CLI arguments with sensible defaults +# - Automatic validation of required parameters +# - Help/usage message +# - Clean logging with timestamps +# - Dry-run mode to preview the command without executing +# ============================================================================== + +set -Eeuo pipefail +IFS=$'\n\t' + +#-------------------------------# +# Default Configuration # +#-------------------------------# +CONCURRENCY=200 +REQUESTS=10000 +METHOD="POST" +CONTENT_TYPE="application/json" +HEADERS=() +PAYLOAD_FILE="payload.json" +URL="http://localhost:4444/rpc" +DRY_RUN=false +LOG_FILE="hey-$(date +%Y%m%d_%H%M%S).log" + +#-------------------------------# +# Helper Functions # +#-------------------------------# +usage() { + cat < Total number of requests (default: $REQUESTS) + -c Number of concurrent workers (default: $CONCURRENCY) + -X HTTP method (default: $METHOD) + -T Content-Type header (default: $CONTENT_TYPE) + -H
Additional header (can be used multiple times) + -d Payload JSON file (default: $PAYLOAD_FILE) + -u Target URL (required) + -l Log output to file (default: $LOG_FILE) + --dry-run Show the `hey` command without running it + -h, --help Display this help and exit + +Example: + ${0##*/} -n 5000 -c 100 \ + -X POST -T application/json \ + -H "Authorization: Bearer \$JWT" \ + -d payload.json \ + -u http://localhost:4444/rpc +EOF + exit 1 +} + +log() { + local ts + ts="$(date +'%Y-%m-%d %H:%M:%S')" + echo "[$ts] $*" | tee -a "$LOG_FILE" +} + +check_command() { + if ! command -v "$1" &>/dev/null; then + echo "ERROR: Required command '$1' not found in PATH." >&2 + exit 2 + fi +} + +#-------------------------------# +# Parse Command-Line # +#-------------------------------# +while (( "$#" )); do + case "$1" in + -n) REQUESTS="$2"; shift 2 ;; + -c) CONCURRENCY="$2"; shift 2 ;; + -X) METHOD="$2"; shift 2 ;; + -T) CONTENT_TYPE="$2"; shift 2 ;; + -H) HEADERS+=("$2"); shift 2 ;; + -d) PAYLOAD_FILE="$2"; shift 2 ;; + -u) URL="$2"; shift 2 ;; + -l) LOG_FILE="$2"; shift 2 ;; + --dry-run) DRY_RUN=true; shift ;; + -h|--help) usage ;; + *) echo "Unknown option: $1" >&2; usage ;; + esac +done + +#-------------------------------# +# Validate Required Inputs # +#-------------------------------# +[[ -z "$URL" ]] && { echo "ERROR: Target URL is required."; usage; } +[[ -f "$PAYLOAD_FILE" ]] || { echo "ERROR: Payload file '$PAYLOAD_FILE' not found."; exit 3; } + +#-------------------------------# +# Dependency Verification # +#-------------------------------# +check_command hey +check_command date +check_command tee + +#-------------------------------# +# Build `hey` Command # +#-------------------------------# +HEY_CMD=( + hey + -n "$REQUESTS" + -c "$CONCURRENCY" + -m "$METHOD" + -T "$CONTENT_TYPE" + -D "$PAYLOAD_FILE" +) + +# Append each header +for hdr in "${HEADERS[@]}"; do + HEY_CMD+=(-H "$hdr") +done + +HEY_CMD+=("$URL") + +#-------------------------------# +# Execute or Dry-Run # +#-------------------------------# +log "Invoking load test:" +log " Requests: $REQUESTS" +log " Concurrency: $CONCURRENCY" +log " Method: $METHOD" +log " Content-Type: $CONTENT_TYPE" +log " Payload file: $PAYLOAD_FILE" +log " URL: $URL" +[[ ${#HEADERS[@]} -gt 0 ]] && log " Headers: ${HEADERS[*]}" +log " Log file: $LOG_FILE" + +if [ "$DRY_RUN" = true ]; then + echo -e "\nDry-run mode: Here’s the command that would be executed:\n" + printf ' %q' "${HEY_CMD[@]}" + echo + exit 0 +fi + +# Run and capture output +"${HEY_CMD[@]}" 2>&1 | tee -a "$LOG_FILE" +EXIT_CODE=${PIPESTATUS[0]} + +if [ "$EXIT_CODE" -ne 0 ]; then + log "ERROR: 'hey' exited with code $EXIT_CODE." + exit "$EXIT_CODE" +else + log "Load test completed successfully." +fi diff --git a/tests/hey/payload.json b/tests/hey/payload.json new file mode 100644 index 0000000000..dce6e39392 --- /dev/null +++ b/tests/hey/payload.json @@ -0,0 +1,10 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "method": "convert_time", + "params": { + "source_timezone": "Europe/Berlin", + "target_timezone": "Europe/Dublin", + "time": "09:00" + } +} diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/integration/test_api_endpoints.py b/tests/integration/test_api_endpoints.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/test_api_endpoints.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/integration/test_federation.py b/tests/integration/test_federation.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/test_federation.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/integration/test_prompt_rendering.py b/tests/integration/test_prompt_rendering.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/test_prompt_rendering.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/integration/test_resource_management.py b/tests/integration/test_resource_management.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/test_resource_management.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/integration/test_server_catalog.py b/tests/integration/test_server_catalog.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/test_server_catalog.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/integration/test_tool_invocation.py b/tests/integration/test_tool_invocation.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/integration/test_tool_invocation.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/__init__.py b/tests/unit/mcpgateway/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/admin/__init__.py b/tests/unit/mcpgateway/admin/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/admin/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/admin/test_routes.py b/tests/unit/mcpgateway/admin/test_routes.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/admin/test_routes.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/cache/__init__.py b/tests/unit/mcpgateway/cache/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/cache/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/cache/test_resource_cache.py b/tests/unit/mcpgateway/cache/test_resource_cache.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/cache/test_resource_cache.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/db/__init__.py b/tests/unit/mcpgateway/db/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/db/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/db/test_init.py b/tests/unit/mcpgateway/db/test_init.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/db/test_init.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/db/test_models.py b/tests/unit/mcpgateway/db/test_models.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/db/test_models.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/federation/__init__.py b/tests/unit/mcpgateway/federation/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/federation/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/federation/test_discovery.py b/tests/unit/mcpgateway/federation/test_discovery.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/federation/test_discovery.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/federation/test_forward.py b/tests/unit/mcpgateway/federation/test_forward.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/federation/test_forward.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/federation/test_manager.py b/tests/unit/mcpgateway/federation/test_manager.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/federation/test_manager.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/handlers/__init__.py b/tests/unit/mcpgateway/handlers/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/handlers/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/handlers/test_sampling.py b/tests/unit/mcpgateway/handlers/test_sampling.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/handlers/test_sampling.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/services/__init__.py b/tests/unit/mcpgateway/services/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/services/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/services/test_completion_service.py b/tests/unit/mcpgateway/services/test_completion_service.py new file mode 100644 index 0000000000..b5001500aa --- /dev/null +++ b/tests/unit/mcpgateway/services/test_completion_service.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +import pytest + +from mcpgateway.services.completion_service import ( # :contentReference[oaicite:0]{index=0}:contentReference[oaicite:1]{index=1} + CompletionError, + CompletionService, +) +from mcpgateway.types import ( + CompleteResult, # :contentReference[oaicite:2]{index=2}:contentReference[oaicite:3]{index=3} +) + + +class FakeScalarOneResult: + def __init__(self, value): + self._value = value + + def scalar_one_or_none(self): + return self._value + + +class FakeScalarsAllResult: + def __init__(self, values): + self._values = values + + def scalars(self): + return self + + def all(self): + return self._values + + +class DummyPrompt: + def __init__(self, name, argument_schema): + self.name = name + self.argument_schema = argument_schema + self.is_active = True + + +class DummyResource: + def __init__(self, uri): + self.uri = uri + self.is_active = True + + +@pytest.mark.asyncio +async def test_handle_completion_missing_ref_or_arg(): + service = CompletionService() + with pytest.raises(CompletionError) as exc: + await service.handle_completion(None, {}) + assert "Missing reference type or argument name" in str(exc.value) + + +@pytest.mark.asyncio +async def test_handle_completion_invalid_ref_type(): + service = CompletionService() + request = {"ref": {"type": "ref/unknown"}, "argument": {"name": "arg", "value": ""}} + with pytest.raises(CompletionError) as exc: + await service.handle_completion(None, request) + assert "Invalid reference type: ref/unknown" in str(exc.value) + + +@pytest.mark.asyncio +async def test_complete_prompt_missing_name(): + service = CompletionService() + with pytest.raises(CompletionError) as exc: + await service._complete_prompt_argument(None, {}, "arg1", "") + assert "Missing prompt name" in str(exc.value) + + +@pytest.mark.asyncio +async def test_complete_prompt_not_found(): + service = CompletionService() + + class DummySession: + def execute(self, query): + return FakeScalarOneResult(None) + + with pytest.raises(CompletionError) as exc: + await service._complete_prompt_argument(DummySession(), {"name": "nonexistent"}, "arg", "") + assert "Prompt not found: nonexistent" in str(exc.value) + + +@pytest.mark.asyncio +async def test_complete_prompt_argument_not_found(): + service = CompletionService() + prompt = DummyPrompt("p1", {"properties": {"p": {"name": "other"}}}) + + class DummySession: + def execute(self, query): + return FakeScalarOneResult(prompt) + + with pytest.raises(CompletionError) as exc: + await service._complete_prompt_argument(DummySession(), {"name": "p1"}, "arg", "") + assert "Argument not found: arg" in str(exc.value) + + +@pytest.mark.asyncio +async def test_complete_prompt_enum_values(): + service = CompletionService() + schema = {"properties": {"p": {"name": "arg1", "enum": ["Apple", "Banana", "Cherry"]}}} + prompt = DummyPrompt("p1", schema) + + class DummySession: + def execute(self, query): + return FakeScalarOneResult(prompt) + + result = await service._complete_prompt_argument(DummySession(), {"name": "p1"}, "arg1", "an") + assert isinstance(result, CompleteResult) + comp = result.completion + assert comp["values"] == ["Banana"] + assert comp["total"] == 1 + assert comp["hasMore"] is False + + +@pytest.mark.asyncio +async def test_custom_completions_override_enum(): + service = CompletionService() + service.register_completions("arg1", ["dog", "cat", "ferret"]) + schema = {"properties": {"p": {"name": "arg1"}}} + prompt = DummyPrompt("p1", schema) + + class DummySession: + def execute(self, query): + return FakeScalarOneResult(prompt) + + result = await service._complete_prompt_argument(DummySession(), {"name": "p1"}, "arg1", "er") + comp = result.completion + assert comp["values"] == ["ferret"] + assert comp["total"] == 1 + assert comp["hasMore"] is False + + +@pytest.mark.asyncio +async def test_complete_resource_missing_uri(): + service = CompletionService() + + class DummySession: + pass + + with pytest.raises(CompletionError) as exc: + await service._complete_resource_uri(DummySession(), {}, "arg", "") + assert "Missing URI template" in str(exc.value) + + +@pytest.mark.asyncio +async def test_complete_resource_values(): + service = CompletionService() + resources = [DummyResource("foo"), DummyResource("bar"), DummyResource("bazfoo")] + + class DummySession: + def execute(self, query): + return FakeScalarsAllResult(resources) + + result = await service._complete_resource_uri(DummySession(), {"uri": "template"}, "arg", "foo") + comp = result.completion + assert set(comp["values"]) == {"foo", "bazfoo"} + assert comp["total"] == 2 + assert comp["hasMore"] is False + + +@pytest.mark.asyncio +async def test_unregister_completions(): + service = CompletionService() + service.register_completions("arg1", ["a", "b"]) + service.unregister_completions("arg1") + schema = {"properties": {"p": {"name": "arg1"}}} + prompt = DummyPrompt("p1", schema) + + class DummySession: + def execute(self, query): + return FakeScalarOneResult(prompt) + + result = await service._complete_prompt_argument(DummySession(), {"name": "p1"}, "arg1", "a") + comp = result.completion + assert comp["values"] == [] + assert comp["total"] == 0 + assert comp["hasMore"] is False diff --git a/tests/unit/mcpgateway/services/test_gateway_service.py b/tests/unit/mcpgateway/services/test_gateway_service.py new file mode 100644 index 0000000000..e89187ebbe --- /dev/null +++ b/tests/unit/mcpgateway/services/test_gateway_service.py @@ -0,0 +1,496 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for the Gateway Service implementation. +""" + +import asyncio +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError + +from mcpgateway.db import Gateway as DbGateway +from mcpgateway.db import Tool as DbTool +from mcpgateway.schemas import GatewayCreate, GatewayRead, GatewayUpdate +from mcpgateway.services.gateway_service import ( + GatewayConnectionError, + GatewayError, + GatewayNameConflictError, + GatewayNotFoundError, + GatewayService, +) + + +@pytest.fixture +def gateway_service(): + """Create a gateway service instance.""" + service = GatewayService() + service._http_client = AsyncMock() + return service + + +@pytest.fixture +def mock_gateway(): + """Create a mock gateway model.""" + gateway = MagicMock(spec=DbGateway) + gateway.id = 1 + gateway.name = "test_gateway" + gateway.url = "http://example.com/gateway" + gateway.description = "A test gateway" + gateway.capabilities = { + "prompts": {"listChanged": True}, + "resources": {"listChanged": True}, + "tools": {"listChanged": True}, + } + gateway.created_at = "2023-01-01T00:00:00" + gateway.updated_at = "2023-01-01T00:00:00" + gateway.is_active = True + gateway.last_seen = "2023-01-01T00:00:00" + + # Set up associated tools + tool1 = MagicMock(spec=DbTool) + tool1.id = 101 + tool1.name = "federated_tool" + + gateway.tools = [tool1] + gateway.federated_tools = [] + + return gateway + + +class TestGatewayService: + """Tests for the GatewayService class.""" + + @pytest.mark.asyncio + async def test_register_gateway(self, gateway_service, test_db): + """Test successful gateway registration.""" + # Set up DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + test_db.add = Mock() + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up gateway service methods + gateway_service._notify_gateway_added = AsyncMock() + gateway_service._initialize_gateway = AsyncMock( + return_value=( + { + "prompts": {"listChanged": True}, + "resources": {"listChanged": True}, + "tools": {"listChanged": True}, + }, + [], + ) + ) + + # Create gateway request + gateway_create = GatewayCreate(name="test_gateway", url="http://example.com/gateway", description="A test gateway") + + # Call method + result = await gateway_service.register_gateway(test_db, gateway_create) + + # Verify DB operations + test_db.add.assert_called_once() + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify gateway initialization + gateway_service._initialize_gateway.assert_called_once_with("http://example.com/gateway") + + # Verify notification + gateway_service._notify_gateway_added.assert_called_once() + + # Verify result + assert result.name == "test_gateway" + assert result.url == "http://example.com/gateway" + assert result.description == "A test gateway" + assert result.is_active is True + + @pytest.mark.asyncio + async def test_register_gateway_name_conflict(self, gateway_service, mock_gateway, test_db): + """Test gateway registration with name conflict.""" + # Mock DB to return existing gateway + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_gateway + test_db.execute = Mock(return_value=mock_scalar) + + # Create gateway request with conflicting name + gateway_create = GatewayCreate(name="test_gateway", url="http://example.com/new-gateway", description="A new gateway") # Same name as mock_gateway + + # Should raise conflict error + with pytest.raises(GatewayNameConflictError) as exc_info: + await gateway_service.register_gateway(test_db, gateway_create) + + assert "Gateway already exists with name" in str(exc_info.value) + assert exc_info.value.name == "test_gateway" + assert exc_info.value.is_active == mock_gateway.is_active + assert exc_info.value.gateway_id == mock_gateway.id + + @pytest.mark.asyncio + async def test_register_gateway_connection_error(self, gateway_service, test_db): + """Test gateway registration with connection error.""" + # Set up DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + test_db.rollback = Mock() + + # Set up gateway service methods to fail initialization + gateway_service._initialize_gateway = AsyncMock(side_effect=GatewayConnectionError("Failed to connect")) + + # Create gateway request + gateway_create = GatewayCreate(name="test_gateway", url="http://example.com/gateway", description="A test gateway") + + # Should raise error + with pytest.raises(GatewayError) as exc_info: + await gateway_service.register_gateway(test_db, gateway_create) + + assert "Failed to register gateway" in str(exc_info.value) + + # Verify rollback + test_db.rollback.assert_called_once() + + @pytest.mark.asyncio + async def test_list_gateways(self, gateway_service, mock_gateway, test_db): + """Test listing gateways.""" + # Mock DB to return a list of gateways + mock_scalar_result = MagicMock() + mock_scalar_result.all.return_value = [mock_gateway] + mock_execute = Mock(return_value=mock_scalar_result) + test_db.execute = mock_execute + + # Call method + result = await gateway_service.list_gateways(test_db) + + # Verify DB query + test_db.execute.assert_called_once() + + # Verify result + assert len(result) == 1 + assert result[0].name == "test_gateway" + assert result[0].url == "http://example.com/gateway" + assert result[0].is_active is True + + @pytest.mark.asyncio + async def test_get_gateway(self, gateway_service, mock_gateway, test_db): + """Test getting a gateway by ID.""" + # Mock DB get to return gateway + test_db.get = Mock(return_value=mock_gateway) + + # Call method + result = await gateway_service.get_gateway(test_db, 1) + + # Verify DB query + test_db.get.assert_called_once_with(DbGateway, 1) + + # Verify result + assert result.name == "test_gateway" + assert result.url == "http://example.com/gateway" + assert result.description == "A test gateway" + assert result.is_active is True + assert result.capabilities == mock_gateway.capabilities + + @pytest.mark.asyncio + async def test_get_gateway_not_found(self, gateway_service, test_db): + """Test getting a non-existent gateway.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Should raise NotFoundError + with pytest.raises(GatewayNotFoundError) as exc_info: + await gateway_service.get_gateway(test_db, 999) + + assert "Gateway not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_get_gateway_inactive(self, gateway_service, mock_gateway, test_db): + """Test getting an inactive gateway.""" + # Set gateway to inactive + mock_gateway.is_active = False + + # Mock DB get to return inactive gateway + test_db.get = Mock(return_value=mock_gateway) + + # Should raise NotFoundError mentioning inactive status + with pytest.raises(GatewayNotFoundError) as exc_info: + await gateway_service.get_gateway(test_db, 1) + + assert "Gateway 'test_gateway' exists but is inactive" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_update_gateway(self, gateway_service, mock_gateway, test_db): + """Test updating a gateway.""" + # Mock DB get to return gateway + test_db.get = Mock(return_value=mock_gateway) + + # Mock DB to check for name conflicts + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up gateway service methods + gateway_service._notify_gateway_updated = AsyncMock() + gateway_service._initialize_gateway = AsyncMock( + return_value=( + { + "prompts": {"listChanged": True, "subscribe": True}, + "resources": {"listChanged": True, "subscribe": True}, + "tools": {"listChanged": True, "subscribe": True}, + }, + [], + ) + ) + + # Create update request + gateway_update = GatewayUpdate(name="updated_gateway", url="http://example.com/updated-gateway", description="An updated gateway") + + # Call method + result = await gateway_service.update_gateway(test_db, 1, gateway_update) + + # Verify DB operations + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify gateway properties were updated + assert mock_gateway.name == "updated_gateway" + assert mock_gateway.url == "http://example.com/updated-gateway" + assert mock_gateway.description == "An updated gateway" + + # Verify gateway reinitialization + gateway_service._initialize_gateway.assert_called_once() + + # Verify notification + gateway_service._notify_gateway_updated.assert_called_once() + + # Verify result + assert result.name == "updated_gateway" + assert result.url == "http://example.com/updated-gateway" + assert result.description == "An updated gateway" + + @pytest.mark.asyncio + async def test_update_gateway_not_found(self, gateway_service, test_db): + """Test updating a non-existent gateway.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Create update request + gateway_update = GatewayUpdate(name="updated_gateway", description="An updated gateway") + + # Should raise NotFoundError + with pytest.raises(GatewayNotFoundError) as exc_info: + await gateway_service.update_gateway(test_db, 999, gateway_update) + + assert "Gateway not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_update_gateway_name_conflict(self, gateway_service, mock_gateway, test_db): + """Test updating a gateway with a name that conflicts with another gateway.""" + # Create a second gateway (the one being updated) + gateway1 = mock_gateway + + # Create a conflicting gateway + gateway2 = MagicMock(spec=DbGateway) + gateway2.id = 2 + gateway2.name = "existing_gateway" + gateway2.is_active = True + + # Mock DB get to return gateway1 + test_db.get = Mock(return_value=gateway1) + + # Mock DB to check for name conflicts and return gateway2 + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = gateway2 + test_db.execute = Mock(return_value=mock_scalar) + + test_db.rollback = Mock() + + # Create update request with conflicting name + gateway_update = GatewayUpdate( + name="existing_gateway", # Name that conflicts with gateway2 + ) + + # Should raise conflict error + with pytest.raises(GatewayNameConflictError) as exc_info: + await gateway_service.update_gateway(test_db, 1, gateway_update) + + assert "Gateway already exists with name" in str(exc_info.value) + assert exc_info.value.name == "existing_gateway" + assert exc_info.value.is_active == gateway2.is_active + assert exc_info.value.gateway_id == gateway2.id + + @pytest.mark.asyncio + async def test_toggle_gateway_status(self, gateway_service, mock_gateway, test_db): + """Test toggling gateway active status.""" + # Mock DB get to return gateway + test_db.get = Mock(return_value=mock_gateway) + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up service methods + gateway_service._notify_gateway_activated = AsyncMock() + gateway_service._notify_gateway_deactivated = AsyncMock() + gateway_service._initialize_gateway = AsyncMock( + return_value=( + { + "prompts": {"listChanged": True}, + "resources": {"listChanged": True}, + "tools": {"listChanged": True}, + }, + [], + ) + ) + + # Mock tool service + mock_tool_service = AsyncMock() + gateway_service.tool_service = mock_tool_service + + # Deactivate the gateway (it's active by default) + result = await gateway_service.toggle_gateway_status(test_db, 1, activate=False) + + # Verify DB operations + test_db.get.assert_called_once_with(DbGateway, 1) + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify properties were updated + assert mock_gateway.is_active is False + + # Verify notification + gateway_service._notify_gateway_deactivated.assert_called_once() + gateway_service._notify_gateway_activated.assert_not_called() + + # Verify tool service called to toggle associated tools + assert mock_tool_service.toggle_tool_status.called + + # Verify result + assert result.is_active is False + + @pytest.mark.asyncio + async def test_delete_gateway(self, gateway_service, mock_gateway, test_db): + """Test deleting a gateway.""" + # Mock DB get to return gateway + test_db.get = Mock(return_value=mock_gateway) + test_db.delete = Mock() + test_db.commit = Mock() + test_db.query = Mock() + query_mock = Mock() + filter_mock = Mock() + test_db.query.return_value = query_mock + query_mock.filter.return_value = filter_mock + filter_mock.delete.return_value = None + + # Set up service methods + gateway_service._notify_gateway_deleted = AsyncMock() + + # Call method + await gateway_service.delete_gateway(test_db, 1) + + # Verify DB operations + test_db.get.assert_called_once_with(DbGateway, 1) + test_db.delete.assert_called_once_with(mock_gateway) + test_db.commit.assert_called() + + # Verify notification + gateway_service._notify_gateway_deleted.assert_called_once() + + @pytest.mark.asyncio + async def test_delete_gateway_not_found(self, gateway_service, test_db): + """Test deleting a non-existent gateway.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Should raise NotFoundError + with pytest.raises(GatewayNotFoundError) as exc_info: + await gateway_service.delete_gateway(test_db, 999) + + assert "Gateway not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_forward_request(self, gateway_service, mock_gateway): + """Test forwarding a request to a gateway.""" + # Set up HTTP client response + mock_response = AsyncMock() + mock_response.raise_for_status = AsyncMock() + mock_response.json.return_value = {"jsonrpc": "2.0", "result": {"success": True, "data": "Response data"}, "id": 1} + gateway_service._http_client.post.return_value = mock_response + + # Call method + result = await gateway_service.forward_request(mock_gateway, "test_method", {"param": "value"}) + + # Verify HTTP request + gateway_service._http_client.post.assert_called_once_with( + f"{mock_gateway.url}/rpc", + json={"jsonrpc": "2.0", "id": 1, "method": "test_method", "params": {"param": "value"}}, + headers=gateway_service._get_auth_headers(), + ) + + # Verify result + assert result == {"success": True, "data": "Response data"} + + # Verify gateway last_seen updated + assert mock_gateway.last_seen is not None + + @pytest.mark.asyncio + async def test_forward_request_error_response(self, gateway_service, mock_gateway): + """Test forwarding a request that returns an error.""" + # Set up HTTP client response + mock_response = AsyncMock() + mock_response.raise_for_status = AsyncMock() + mock_response.json.return_value = {"jsonrpc": "2.0", "error": {"code": -32000, "message": "Test error"}, "id": 1} + gateway_service._http_client.post.return_value = mock_response + + # Should raise GatewayError + with pytest.raises(GatewayError) as exc_info: + await gateway_service.forward_request(mock_gateway, "test_method", {"param": "value"}) + + assert "Gateway error: Test error" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_forward_request_connection_error(self, gateway_service, mock_gateway): + """Test forwarding a request with connection error.""" + # Set up HTTP client to raise exception + gateway_service._http_client.post.side_effect = Exception("Connection error") + + # Should raise GatewayError + with pytest.raises(GatewayError) as exc_info: + await gateway_service.forward_request(mock_gateway, "test_method", {"param": "value"}) + + assert "Failed to forward request" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_check_gateway_health(self, gateway_service, mock_gateway): + """Test checking gateway health.""" + # Set up _initialize_gateway to succeed + gateway_service._initialize_gateway = AsyncMock() + + # Call method + result = await gateway_service.check_gateway_health(mock_gateway) + + # Verify result + assert result is True + + # Verify gateway last_seen updated + assert mock_gateway.last_seen is not None + + @pytest.mark.asyncio + async def test_check_gateway_health_failure(self, gateway_service, mock_gateway): + """Test checking gateway health with failure.""" + # Set up _initialize_gateway to fail + gateway_service._initialize_gateway = AsyncMock(side_effect=Exception("Health check failed")) + + # Call method + result = await gateway_service.check_gateway_health(mock_gateway) + + # Verify result + assert result is False diff --git a/tests/unit/mcpgateway/services/test_logging_service.py b/tests/unit/mcpgateway/services/test_logging_service.py new file mode 100644 index 0000000000..f6d9f94ffb --- /dev/null +++ b/tests/unit/mcpgateway/services/test_logging_service.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +import asyncio +import logging +from datetime import datetime + +import pytest + +from mcpgateway.services.logging_service import LoggingService # noqa: E402 +from mcpgateway.types import LogLevel # noqa: E402 + + +@pytest.mark.asyncio +async def test_should_log_default_levels(): + service = LoggingService() + # Default level is INFO + assert not service._should_log(LogLevel.DEBUG) + assert service._should_log(LogLevel.INFO) + assert service._should_log(LogLevel.ERROR) + + +@pytest.mark.asyncio +async def test_get_logger_sets_level_and_reuses_instance(): + service = LoggingService() + # Default level INFO + logger1 = service.get_logger("test") + assert logger1.level == logging.INFO + + # Subsequent get_logger returns the same instance + logger2 = service.get_logger("test") + assert logger1 is logger2 + + # Change service level to DEBUG and verify new logger gets updated level + await service.set_level(LogLevel.DEBUG) + logger3 = service.get_logger("newlogger") + assert logger3.level == logging.DEBUG + + +@pytest.mark.asyncio +async def test_notify_without_subscribers_logs_via_standard_logging(caplog): + service = LoggingService() + caplog.set_level(logging.INFO) + # No subscribers: should not raise + await service.notify("standalone message", LogLevel.INFO) + # Standard logging should have captured the message + assert "standalone message" in caplog.text + + +@pytest.mark.asyncio +async def test_notify_below_threshold_does_not_send_to_subscribers(): + service = LoggingService() + events = [] + + async def subscriber(): + async for msg in service.subscribe(): + events.append(msg) + + task = asyncio.create_task(subscriber()) + # Send DEBUG while level is INFO: should be skipped + await service.notify("debug msg", LogLevel.DEBUG) + # Give a moment for any (unexpected) deliveries + await asyncio.sleep(0.1) + assert events == [] + + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + + +@pytest.mark.asyncio +async def test_notify_and_subscribe_receive_message_with_metadata(): + service = LoggingService() + events = [] + + async def subscriber(): + async for msg in service.subscribe(): + events.append(msg) + break + + task = asyncio.create_task(subscriber()) + await service.notify("hello world", LogLevel.INFO, logger_name="mylogger") + await asyncio.wait_for(task, timeout=1.0) + + assert len(events) == 1 + evt = events[0] + # Check structure + assert evt["type"] == "log" + data = evt["data"] + assert data["level"] == LogLevel.INFO + assert data["data"] == "hello world" + # Timestamp is ISO-format parsable + datetime.fromisoformat(data["timestamp"]) + # Logger name included + assert data["logger"] == "mylogger" + + # Clean up + await service.shutdown() + + +@pytest.mark.asyncio +async def test_set_level_updates_all_loggers_and_sends_info_notification(): + service = LoggingService() + events = [] + + async def subscriber(): + async for msg in service.subscribe(): + events.append(msg) + break + + task = asyncio.create_task(subscriber()) + # Set to WARNING + await service.set_level(LogLevel.WARNING) + await asyncio.wait_for(task, timeout=1.0) + + # Verify notification event + assert len(events) == 1 + evt = events[0] + assert evt["type"] == "log" + data = evt["data"] + assert data["level"] == LogLevel.INFO + assert "Log level set to WARNING" in data["data"] + + # Existing root logger level updated + root_logger = service.get_logger("") + assert root_logger.level == logging.WARNING + + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + + +@pytest.mark.asyncio +async def test_subscribe_cleanup_removes_queue_on_cancel(): + service = LoggingService() + # No subscribers initially + assert len(service._subscribers) == 0 + + # Start subscription but don't yield any events + agen = service.subscribe() + task = asyncio.create_task(agen.__anext__()) + + # Subscriber should be registered + await asyncio.sleep(0) # allow subscription setup + assert len(service._subscribers) == 1 + + # Cancel the pending next() to trigger cleanup + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + + # Subscriber should have been removed + assert len(service._subscribers) == 0 diff --git a/tests/unit/mcpgateway/services/test_prompt_service.py b/tests/unit/mcpgateway/services/test_prompt_service.py new file mode 100644 index 0000000000..084c9ee2d1 --- /dev/null +++ b/tests/unit/mcpgateway/services/test_prompt_service.py @@ -0,0 +1,602 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for prompt service implementation. +""" + +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError + +from mcpgateway.db import Prompt as DbPrompt +from mcpgateway.schemas import PromptArgument, PromptCreate, PromptRead, PromptUpdate +from mcpgateway.services.prompt_service import ( + PromptError, + PromptNameConflictError, + PromptNotFoundError, + PromptService, + PromptValidationError, +) +from mcpgateway.types import Message, PromptResult, Role, TextContent + + +@pytest.fixture +def prompt_service(): + """Create a prompt service instance.""" + return PromptService() + + +@pytest.fixture +def mock_prompt(): + """Create a mock prompt model.""" + prompt = MagicMock(spec=DbPrompt) + prompt.id = 1 + prompt.name = "test_prompt" + prompt.description = "A test prompt" + prompt.template = "This is a template with {{ param }}." + prompt.argument_schema = {"type": "object", "properties": {"param": {"type": "string", "description": "A parameter"}}, "required": ["param"]} + prompt.created_at = "2023-01-01T00:00:00" + prompt.updated_at = "2023-01-01T00:00:00" + prompt.is_active = True + + # Set up metrics + prompt.metrics = [] + prompt.execution_count = 0 + prompt.successful_executions = 0 + prompt.failed_executions = 0 + prompt.failure_rate = 0.0 + prompt.min_response_time = None + prompt.max_response_time = None + prompt.avg_response_time = None + prompt.last_execution_time = None + + # Set up validate_arguments + prompt.validate_arguments = Mock() + + return prompt + + +class TestPromptService: + """Tests for the PromptService class.""" + + @pytest.mark.asyncio + async def test_register_prompt(self, prompt_service, test_db): + """Test successful prompt registration.""" + # Set up DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + test_db.add = Mock() + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up prompt service methods + prompt_service._notify_prompt_added = AsyncMock() + prompt_service._validate_template = Mock() + prompt_service._get_required_arguments = Mock(return_value=set(["param"])) + prompt_service._convert_db_prompt = Mock( + return_value={ + "id": 1, + "name": "test_prompt", + "description": "A test prompt", + "template": "This is a template with {{ param }}.", + "arguments": [{"name": "param", "description": "", "required": True}], + "created_at": "2023-01-01T00:00:00", + "updated_at": "2023-01-01T00:00:00", + "is_active": True, + "metrics": { + "totalExecutions": 0, + "successfulExecutions": 0, + "failedExecutions": 0, + "failureRate": 0.0, + "minResponseTime": None, + "maxResponseTime": None, + "avgResponseTime": None, + "lastExecutionTime": None, + }, + } + ) + + # Create prompt request + prompt_create = PromptCreate( + name="test_prompt", description="A test prompt", template="This is a template with {{ param }}.", arguments=[PromptArgument(name="param", description="A parameter", required=True)] + ) + + # Call method + result = await prompt_service.register_prompt(test_db, prompt_create) + + # Verify DB operations + test_db.add.assert_called_once() + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify validation and notification + prompt_service._validate_template.assert_called_once_with(prompt_create.template) + prompt_service._notify_prompt_added.assert_called_once() + + # Verify result + assert result.name == "test_prompt" + assert result.description == "A test prompt" + assert result.is_active is True + assert any(arg.name == "param" for arg in result.arguments) + + @pytest.mark.asyncio + async def test_register_prompt_name_conflict(self, prompt_service, mock_prompt, test_db): + """Test prompt registration with name conflict.""" + # Mock DB to return existing prompt + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_prompt + test_db.execute = Mock(return_value=mock_scalar) + + # Create prompt request with conflicting name + prompt_create = PromptCreate(name="test_prompt", description="A new prompt", template="New template", arguments=[]) # Same name as mock_prompt + + # Should raise conflict error + with pytest.raises(PromptNameConflictError) as exc_info: + await prompt_service.register_prompt(test_db, prompt_create) + + assert "Prompt already exists with name" in str(exc_info.value) + assert exc_info.value.name == "test_prompt" + assert exc_info.value.is_active == mock_prompt.is_active + assert exc_info.value.prompt_id == mock_prompt.id + + @pytest.mark.asyncio + async def test_register_prompt_invalid_template(self, prompt_service, test_db): + """Test prompt registration with invalid template.""" + # Mock DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + # Set up prompt service to fail template validation + prompt_service._validate_template = Mock(side_effect=PromptValidationError("Invalid template syntax")) + + # Create prompt request with invalid template + prompt_create = PromptCreate(name="test_prompt", description="A test prompt", template="This is a template with {{ invalid syntax.", arguments=[]) # Invalid Jinja template + + # Should raise validation error + with pytest.raises(PromptError) as exc_info: + await prompt_service.register_prompt(test_db, prompt_create) + + assert "Failed to register prompt" in str(exc_info.value) + test_db.rollback.assert_called_once() + + @pytest.mark.asyncio + async def test_list_prompts(self, prompt_service, mock_prompt, test_db): + """Test listing prompts.""" + # Mock DB to return a list of prompts + mock_scalar_result = MagicMock() + mock_scalar_result.all.return_value = [mock_prompt] + mock_execute = Mock(return_value=mock_scalar_result) + test_db.execute = mock_execute + + # Set up conversion + prompt_read = PromptRead( + id=1, + name="test_prompt", + description="A test prompt", + template="This is a template with {{ param }}.", + arguments=[PromptArgument(name="param", description="A parameter", required=True)], + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + prompt_service._convert_db_prompt = Mock(return_value=prompt_read.model_dump(by_alias=True)) + + # Call method + result = await prompt_service.list_prompts(test_db) + + # Verify DB query + test_db.execute.assert_called_once() + + # Verify result + assert len(result) == 1 + assert result[0].name == "test_prompt" + assert result[0].template == "This is a template with {{ param }}." + prompt_service._convert_db_prompt.assert_called_once_with(mock_prompt) + + @pytest.mark.asyncio + async def test_get_prompt(self, prompt_service, mock_prompt, test_db): + """Test getting and rendering a prompt with arguments.""" + # Mock DB to return prompt + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_prompt + test_db.execute = Mock(return_value=mock_scalar) + + # Set up prompt service methods + prompt_service._render_template = Mock(return_value="This is a template with test value.") + prompt_service._parse_messages = Mock(return_value=[Message(role=Role.USER, content=TextContent(type="text", text="This is a template with test value."))]) + + # Call method with arguments + result = await prompt_service.get_prompt(test_db, "test_prompt", {"param": "test value"}) + + # Verify template rendering + mock_prompt.validate_arguments.assert_called_once_with({"param": "test value"}) + prompt_service._render_template.assert_called_once_with(mock_prompt.template, {"param": "test value"}) + + # Verify result + assert isinstance(result, PromptResult) + assert len(result.messages) == 1 + assert result.messages[0].role == Role.USER + assert result.messages[0].content.text == "This is a template with test value." + + @pytest.mark.asyncio + async def test_get_prompt_no_arguments(self, prompt_service, mock_prompt, test_db): + """Test getting a prompt without arguments (template information only).""" + # Mock DB to return prompt + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_prompt + test_db.execute = Mock(return_value=mock_scalar) + + # Call method without arguments + result = await prompt_service.get_prompt(test_db, "test_prompt", {}) + + # Verify no rendering happened + mock_prompt.validate_arguments.assert_not_called() + + # Verify result contains template information + assert isinstance(result, PromptResult) + assert len(result.messages) == 1 + assert result.messages[0].role == Role.USER + assert result.messages[0].content.text == mock_prompt.template + + @pytest.mark.asyncio + async def test_get_prompt_not_found(self, prompt_service, test_db): + """Test getting a non-existent prompt.""" + # Mock DB to return None for active prompts + mock_scalar1 = Mock() + mock_scalar1.scalar_one_or_none.return_value = None + # Mock DB to return None for inactive prompts + mock_scalar2 = Mock() + mock_scalar2.scalar_one_or_none.return_value = None + test_db.execute = Mock(side_effect=[mock_scalar1, mock_scalar2]) + + # Should raise NotFoundError + with pytest.raises(PromptNotFoundError) as exc_info: + await prompt_service.get_prompt(test_db, "nonexistent_prompt", {}) + + assert "Prompt not found: nonexistent_prompt" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_get_prompt_inactive(self, prompt_service, mock_prompt, test_db): + """Test getting an inactive prompt.""" + # Set prompt to inactive + mock_prompt.is_active = False + + # Mock DB to return None for active prompts, but return the inactive prompt + mock_scalar1 = Mock() + mock_scalar1.scalar_one_or_none.return_value = None + + mock_scalar2 = Mock() + mock_scalar2.scalar_one_or_none.return_value = mock_prompt + + test_db.execute = Mock(side_effect=[mock_scalar1, mock_scalar2]) + + # Should raise NotFoundError with "inactive" message + with pytest.raises(PromptNotFoundError) as exc_info: + await prompt_service.get_prompt(test_db, "test_prompt", {}) + + assert "Prompt 'test_prompt' exists but is inactive" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_update_prompt(self, prompt_service, mock_prompt, test_db): + """Test updating a prompt.""" + # Mock DB to return prompt + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_prompt + test_db.execute = Mock(return_value=mock_scalar) + + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up prompt service methods + prompt_service._notify_prompt_updated = AsyncMock() + prompt_service._validate_template = Mock() + prompt_service._get_required_arguments = Mock(return_value=set(["new_param"])) + prompt_service._convert_db_prompt = Mock( + return_value={ + "id": 1, + "name": "updated_prompt", + "description": "An updated prompt", + "template": "This is an updated template with {{ new_param }}.", + "arguments": [{"name": "new_param", "description": "A new parameter", "required": True}], + "created_at": "2023-01-01T00:00:00", + "updated_at": "2023-01-01T00:00:00", + "is_active": True, + "metrics": { + "totalExecutions": 0, + "successfulExecutions": 0, + "failedExecutions": 0, + "failureRate": 0.0, + "minResponseTime": None, + "maxResponseTime": None, + "avgResponseTime": None, + "lastExecutionTime": None, + }, + } + ) + + # Create update request + prompt_update = PromptUpdate( + name="updated_prompt", + description="An updated prompt", + template="This is an updated template with {{ new_param }}.", + arguments=[PromptArgument(name="new_param", description="A new parameter", required=True)], + ) + + # Call method + result = await prompt_service.update_prompt(test_db, "test_prompt", prompt_update) + + # Verify DB operations + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify prompt properties were updated + assert mock_prompt.name == "updated_prompt" + assert mock_prompt.description == "An updated prompt" + assert mock_prompt.template == "This is an updated template with {{ new_param }}." + + # Verify validation and notification + prompt_service._validate_template.assert_called_once_with(prompt_update.template) + prompt_service._notify_prompt_updated.assert_called_once() + + # Verify result + assert result.name == "updated_prompt" + assert result.description == "An updated prompt" + assert result.template == "This is an updated template with {{ new_param }}." + assert any(arg["name"] == "new_param" for arg in result.arguments) + + @pytest.mark.asyncio + async def test_update_prompt_not_found(self, prompt_service, test_db): + """Test updating a non-existent prompt.""" + # Mock DB to return None + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + # Create update request + prompt_update = PromptUpdate( + name="updated_prompt", + description="An updated prompt", + template="Updated template", + ) + + # Should raise NotFoundError + with pytest.raises(PromptNotFoundError) as exc_info: + await prompt_service.update_prompt(test_db, "nonexistent_prompt", prompt_update) + + assert "Prompt not found: nonexistent_prompt" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_update_prompt_name_conflict(self, prompt_service, mock_prompt, test_db): + """Test updating a prompt with a name that conflicts with another prompt.""" + # Create a second prompt (the one being updated) + prompt1 = mock_prompt + + # Create a conflicting prompt + prompt2 = MagicMock(spec=DbPrompt) + prompt2.id = 2 + prompt2.name = "existing_prompt" + prompt2.is_active = True + + # Mock DB to return prompt1 for the first query and prompt2 for the second query + mock_scalar1 = Mock() + mock_scalar1.scalar_one_or_none.return_value = prompt1 + + mock_scalar2 = Mock() + mock_scalar2.scalar_one_or_none.return_value = prompt2 + + test_db.execute = Mock(side_effect=[mock_scalar1, mock_scalar2]) + test_db.rollback = Mock() + + # Create update request with conflicting name + prompt_update = PromptUpdate( + name="existing_prompt", # Name that conflicts with prompt2 + ) + + # Should raise conflict error + with pytest.raises(PromptNameConflictError) as exc_info: + await prompt_service.update_prompt(test_db, "test_prompt", prompt_update) + + assert "Prompt already exists with name" in str(exc_info.value) + assert exc_info.value.name == "existing_prompt" + assert exc_info.value.is_active == prompt2.is_active + assert exc_info.value.prompt_id == prompt2.id + + # Verify rollback + test_db.rollback.assert_called_once() + + @pytest.mark.asyncio + async def test_delete_prompt(self, prompt_service, mock_prompt, test_db): + """Test deleting a prompt.""" + # Mock DB to return prompt + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_prompt + test_db.execute = Mock(return_value=mock_scalar) + + test_db.delete = Mock() + test_db.commit = Mock() + + # Set up prompt service methods + prompt_service._notify_prompt_deleted = AsyncMock() + + # Call method + await prompt_service.delete_prompt(test_db, "test_prompt") + + # Verify DB operations + test_db.delete.assert_called_once_with(mock_prompt) + test_db.commit.assert_called_once() + + # Verify notification + prompt_service._notify_prompt_deleted.assert_called_once() + + @pytest.mark.asyncio + async def test_delete_prompt_not_found(self, prompt_service, test_db): + """Test deleting a non-existent prompt.""" + # Mock DB to return None + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + # Should raise NotFoundError + with pytest.raises(PromptNotFoundError) as exc_info: + await prompt_service.delete_prompt(test_db, "nonexistent_prompt") + + assert "Prompt not found: nonexistent_prompt" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_toggle_prompt_status(self, prompt_service, mock_prompt, test_db): + """Test toggling prompt active status.""" + # Mock DB to return prompt + test_db.get = Mock(return_value=mock_prompt) + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up prompt service methods + prompt_service._notify_prompt_activated = AsyncMock() + prompt_service._notify_prompt_deactivated = AsyncMock() + prompt_service._convert_db_prompt = Mock( + return_value={ + "id": 1, + "name": "test_prompt", + "description": "A test prompt", + "template": "This is a template with {{ param }}.", + "arguments": [{"name": "param", "description": "A parameter", "required": True}], + "created_at": "2023-01-01T00:00:00", + "updated_at": "2023-01-01T00:00:00", + "is_active": False, # Deactivated + "metrics": { + "totalExecutions": 0, + "successfulExecutions": 0, + "failedExecutions": 0, + "failureRate": 0.0, + "minResponseTime": None, + "maxResponseTime": None, + "avgResponseTime": None, + "lastExecutionTime": None, + }, + } + ) + + # Deactivate the prompt (it's active by default) + result = await prompt_service.toggle_prompt_status(test_db, 1, activate=False) + + # Verify DB operations + test_db.get.assert_called_once_with(DbPrompt, 1) + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify properties were updated + assert mock_prompt.is_active is False + + # Verify notification + prompt_service._notify_prompt_deactivated.assert_called_once() + prompt_service._notify_prompt_activated.assert_not_called() + + # Verify result + assert result.is_active is False + + @pytest.mark.asyncio + async def test_reset_metrics(self, prompt_service, test_db): + """Test resetting metrics.""" + # Mock DB operations + test_db.execute = Mock() + test_db.commit = Mock() + + # Call method + await prompt_service.reset_metrics(test_db) + + # Verify DB operations + test_db.execute.assert_called_once() + test_db.commit.assert_called_once() + + def test_validate_template(self, prompt_service): + """Test template validation.""" + # Valid template + prompt_service._validate_template("Hello {{ name }}!") + + # Invalid template + with pytest.raises(PromptValidationError): + prompt_service._validate_template("Hello {{ name") + + def test_get_required_arguments(self, prompt_service): + """Test extraction of required arguments from template.""" + # Test Jinja2 style variables + template = "Hello {{ name }}! Your age is {{ age }}." + args = prompt_service._get_required_arguments(template) + assert "name" in args + assert "age" in args + assert len(args) == 2 + + # Test format style variables + template = "Hello {name}! Your age is {age}." + args = prompt_service._get_required_arguments(template) + assert "name" in args + assert "age" in args + assert len(args) == 2 + + # Test mixed styles + template = "Hello {name}! Your age is {{ age }}." + args = prompt_service._get_required_arguments(template) + assert "name" in args + assert "age" in args + assert len(args) == 2 + + def test_render_template(self, prompt_service): + """Test template rendering.""" + # Test Jinja2 rendering + template = "Hello {{ name }}!" + rendered = prompt_service._render_template(template, {"name": "World"}) + assert rendered == "Hello World!" + + # Test format string fallback + template = "Hello {name}!" + rendered = prompt_service._render_template(template, {"name": "World"}) + assert rendered == "Hello World!" + + # Test with missing arguments + with pytest.raises(PromptError): + prompt_service._render_template("Hello {{ missing }}!", {}) + + def test_parse_messages(self, prompt_service): + """Test parsing rendered text into messages.""" + # Simple user message + text = "This is a user message." + messages = prompt_service._parse_messages(text) + assert len(messages) == 1 + assert messages[0].role == Role.USER + assert messages[0].content.text == "This is a user message." + + # Conversation with user and assistant + text = "# User: This is a user message.\n# Assistant: This is an assistant response." + messages = prompt_service._parse_messages(text) + assert len(messages) == 2 + assert messages[0].role == Role.USER + assert messages[0].content.text == "This is a user message." + assert messages[1].role == Role.ASSISTANT + assert messages[1].content.text == "This is an assistant response." + + # Multiple messages with same role + text = "# User: First user message.\n# User: Second user message." + messages = prompt_service._parse_messages(text) + assert len(messages) == 2 + assert messages[0].role == Role.USER + assert messages[0].content.text == "First user message." + assert messages[1].role == Role.USER + assert messages[1].content.text == "Second user message." diff --git a/tests/unit/mcpgateway/services/test_resource_service.py b/tests/unit/mcpgateway/services/test_resource_service.py new file mode 100644 index 0000000000..3cb854d5e1 --- /dev/null +++ b/tests/unit/mcpgateway/services/test_resource_service.py @@ -0,0 +1,235 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest + +from mcpgateway.schemas import ResourceCreate, ResourceRead +from mcpgateway.services.resource_service import ( + ResourceError, + ResourceNotFoundError, + ResourceService, + ResourceURIConflictError, +) + + +@pytest.fixture +def resource_service(): + """Create a ResourceService instance.""" + return ResourceService() + + +@pytest.fixture +def mock_resource(): + """Create a mock resource model.""" + resource = Mock() + resource.id = 1 + resource.uri = "test/resource" + resource.name = "Test Resource" + resource.description = "A test resource" + resource.mime_type = "text/plain" + resource.size = 12 + resource.is_active = True + resource.created_at = "2023-01-01T00:00:00" + resource.updated_at = "2023-01-01T00:00:00" + resource.text_content = "Test content" + resource.binary_content = None + resource.metrics = [] + resource.__dict__ = { + "id": 1, + "uri": "test/resource", + "name": "Test Resource", + "description": "A test resource", + "mime_type": "text/plain", + "size": 12, + "is_active": True, + "created_at": "2023-01-01T00:00:00", + "updated_at": "2023-01-01T00:00:00", + "text_content": "Test content", + "binary_content": None, + } + + # Create a mock for the content property + content_mock = Mock() + content_mock.type = "resource" + content_mock.uri = "test/resource" + content_mock.mime_type = "text/plain" + content_mock.text = "Test content" + content_mock.blob = None + + # Set up the content property as a property that returns the mock + type(resource).content = content_mock + + return resource + + +class TestResourceService: + """Tests for the ResourceService class.""" + + @pytest.mark.asyncio + async def test_register_resource(self, resource_service, mock_resource, test_db): + """Test successful resource registration.""" + # Create a resource request + create_resource = ResourceCreate( + uri="test/resource", + name="Test Resource", + description="A test resource", + mime_type="text/plain", + content="Test content", + ) + + # Set up mocks for database session + mock_scalar = Mock() + mock_scalar.scalar_one_or_none = Mock(return_value=None) + + test_db.execute = Mock(return_value=mock_scalar) + test_db.add = Mock() + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up mock for resource_service methods + resource_service._is_valid_uri = Mock(return_value=True) + resource_service._detect_mime_type = Mock(return_value="text/plain") + resource_service._notify_resource_added = AsyncMock() + resource_service._convert_resource_to_read = Mock( + return_value=ResourceRead( + id=1, + uri="test/resource", + name="Test Resource", + description="A test resource", + mime_type="text/plain", + size=12, + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ) + + # Call the method under test + result = await resource_service.register_resource(test_db, create_resource) + + # Verify result + assert result.uri == "test/resource" + assert result.name == "Test Resource" + assert result.description == "A test resource" + assert result.mime_type == "text/plain" + assert result.is_active is True + + # Verify method calls + test_db.add.assert_called_once() + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + resource_service._notify_resource_added.assert_called_once() + + @pytest.mark.asyncio + async def test_register_resource_conflict(self, resource_service, mock_resource, test_db): + """Test resource registration with URI conflict.""" + create_resource = ResourceCreate( + uri="existing/resource", + name="Existing Resource", + description="An existing resource", + mime_type="text/plain", + content="Existing content", + ) + + # Mock that a resource with same URI exists + mock_scalar = Mock() + mock_scalar.scalar_one_or_none = Mock(return_value=mock_resource) + test_db.execute = Mock(return_value=mock_scalar) + + with pytest.raises(ResourceURIConflictError) as exc_info: + await resource_service.register_resource(test_db, create_resource) + + assert "Resource already exists with URI" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_read_resource(self, resource_service, mock_resource, test_db): + """Test reading a resource.""" + uri = "test/resource" + + # Mock that the resource exists + mock_scalar = Mock() + mock_scalar.scalar_one_or_none = Mock(return_value=mock_resource) + test_db.execute = Mock(return_value=mock_scalar) + + # Make a mock for the content property + content_mock = MagicMock() + type(mock_resource).content = property(lambda self: content_mock) + + result = await resource_service.read_resource(test_db, uri) + + # Verify result + assert result == content_mock + + @pytest.mark.asyncio + async def test_read_resource_not_found(self, resource_service, test_db): + """Test reading a non-existent resource.""" + uri = "nonexistent/resource" + + # Mock that the resource doesn't exist + mock_scalar = Mock() + mock_scalar.scalar_one_or_none = Mock(return_value=None) + test_db.execute = Mock(return_value=mock_scalar) + + with pytest.raises(ResourceNotFoundError) as exc_info: + await resource_service.read_resource(test_db, uri) + + assert "Resource not found" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_delete_resource(self, resource_service, mock_resource, test_db): + """Test deleting a resource.""" + uri = "test/resource" + + # Mock that the resource exists + mock_scalar = Mock() + mock_scalar.scalar_one_or_none = Mock(return_value=mock_resource) + test_db.execute = Mock(return_value=mock_scalar) + + # Mock db operations + test_db.delete = Mock() + test_db.commit = Mock() + + # Mock notification + resource_service._notify_resource_deleted = AsyncMock() + + # Call the method under test + await resource_service.delete_resource(test_db, uri) + + # Verify DB operations + test_db.delete.assert_called_once_with(mock_resource) + test_db.commit.assert_called_once() + + # Verify notification + resource_service._notify_resource_deleted.assert_called_once() + + @pytest.mark.parametrize( + "uri, expected_result", + [ + ("http://example.com/test", True), + ("https://example.com/test", True), + ("file:///path/to/resource", True), + ("invalid-uri", False), + ], + ) + def test_is_valid_uri(self, resource_service, uri, expected_result): + """Test URI validation.""" + # Use the actual method rather than mocking + result = resource_service._is_valid_uri(uri) + assert result == expected_result diff --git a/tests/unit/mcpgateway/services/test_root_service.py b/tests/unit/mcpgateway/services/test_root_service.py new file mode 100644 index 0000000000..d3a2cc1265 --- /dev/null +++ b/tests/unit/mcpgateway/services/test_root_service.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +import asyncio +import os +from urllib.parse import urlparse + +import pytest + +from mcpgateway.config import settings +from mcpgateway.services.root_service import RootService, RootServiceError +from mcpgateway.types import Root + + +@pytest.mark.asyncio +async def test_add_root_file_uri_and_name(tmp_path): + service = RootService() + # Add a filesystem path without a scheme + dir_path = tmp_path / "mydir" + # (no need to actually create it on disk for URI logic) + root = await service.add_root(str(dir_path)) + # Should prefix with file:// + expected_uri = f"file://{dir_path}" + assert root.uri == expected_uri + # Name should be the basename of the path + assert root.name == os.path.basename(urlparse(expected_uri).path) + + await service.shutdown() + + +@pytest.mark.asyncio +async def test_add_root_with_scheme(): + service = RootService() + # Add an HTTP URI + uri = "http://example.com/base/path" + root = await service.add_root(uri) + # Should remain unchanged + assert root.uri == uri + # Name should be the basename of the URL path + assert root.name == os.path.basename(urlparse(uri).path) + + await service.shutdown() + + +@pytest.mark.asyncio +async def test_add_root_duplicate_raises(): + service = RootService() + uri = "http://example.com/foo" + await service.add_root(uri) + with pytest.raises(RootServiceError) as excinfo: + await service.add_root(uri) + assert "Root already exists" in str(excinfo.value) + + await service.shutdown() + + +@pytest.mark.asyncio +async def test_remove_root_and_list(): + service = RootService() + uri = "http://example.com/to-remove" + root = await service.add_root(uri) + # Ensure it's listed + roots = await service.list_roots() + assert any(r.uri == uri for r in roots) + + # Remove it + await service.remove_root(uri) + roots_after = await service.list_roots() + assert all(r.uri != uri for r in roots_after) + + await service.shutdown() + + +@pytest.mark.asyncio +async def test_remove_nonexistent_root_raises(): + service = RootService() + with pytest.raises(RootServiceError) as excinfo: + await service.remove_root("http://no.such.root") + assert "Root not found" in str(excinfo.value) + + await service.shutdown() + + +@pytest.mark.asyncio +async def test_initialize_adds_default_roots(monkeypatch): + # Monkeypatch default_roots in settings + monkeypatch.setattr(settings, "default_roots", ["http://a.com", "local/path"]) + service = RootService() + await service.initialize() + roots = await service.list_roots() + uris = {r.uri for r in roots} + assert "http://a.com" in uris + assert "file://local/path" in uris + + await service.shutdown() + + +@pytest.mark.asyncio +async def test_subscribe_changes_receives_events(): + service = RootService() + # Start subscription + events = [] + + async def subscriber(): + async for ev in service.subscribe_changes(): + events.append(ev) + if len(events) >= 2: + break + + task = asyncio.create_task(subscriber()) + # Add and remove a root + r = await service.add_root("subscriber-test") + await service.remove_root(r.uri) + # Wait for subscriber to collect both events + await asyncio.wait_for(task, timeout=1.0) + + # Validate the events + assert events[0] == { + "type": "root_added", + "data": {"uri": r.uri, "name": r.name}, + } + assert events[1] == { + "type": "root_removed", + "data": {"uri": r.uri}, + } + + # After breaking, the subscription should be cleaned up without error + await service.shutdown() diff --git a/tests/unit/mcpgateway/services/test_server_service.py b/tests/unit/mcpgateway/services/test_server_service.py new file mode 100644 index 0000000000..8c7f807892 --- /dev/null +++ b/tests/unit/mcpgateway/services/test_server_service.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for server service implementation. +""" + +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError + +from mcpgateway.db import Prompt as DbPrompt +from mcpgateway.db import Resource as DbResource +from mcpgateway.db import Server as DbServer +from mcpgateway.db import Tool as DbTool +from mcpgateway.schemas import ServerCreate, ServerRead, ServerUpdate +from mcpgateway.services.server_service import ( + ServerError, + ServerNameConflictError, + ServerNotFoundError, + ServerService, +) + + +@pytest.fixture +def server_service(): + """Create a server service instance.""" + return ServerService() + + +@pytest.fixture +def mock_server(): + """Create a mock server model.""" + server = MagicMock(spec=DbServer) + server.id = 1 + server.name = "test_server" + server.description = "A test server" + server.icon = "server-icon" + server.created_at = "2023-01-01T00:00:00" + server.updated_at = "2023-01-01T00:00:00" + server.is_active = True + + # Set up associated items + tool1 = MagicMock(spec=DbTool) + tool1.id = 101 + + resource1 = MagicMock(spec=DbResource) + resource1.id = 201 + + prompt1 = MagicMock(spec=DbPrompt) + prompt1.id = 301 + + server.tools = [tool1] + server.resources = [resource1] + server.prompts = [prompt1] + + # Set up metrics + server.metrics = [] + server.execution_count = 0 + server.successful_executions = 0 + server.failed_executions = 0 + server.failure_rate = 0.0 + server.min_response_time = None + server.max_response_time = None + server.avg_response_time = None + server.last_execution_time = None + + return server + + +@pytest.fixture +def mock_tool(): + """Create a mock tool.""" + tool = MagicMock(spec=DbTool) + tool.id = 101 + tool.name = "test_tool" + return tool + + +@pytest.fixture +def mock_resource(): + """Create a mock resource.""" + resource = MagicMock(spec=DbResource) + resource.id = 201 + resource.name = "test_resource" + return resource + + +@pytest.fixture +def mock_prompt(): + """Create a mock prompt.""" + prompt = MagicMock(spec=DbPrompt) + prompt.id = 301 + prompt.name = "test_prompt" + return prompt + + +class TestServerService: + """Tests for the ServerService class.""" + + @pytest.mark.asyncio + async def test_register_server(self, server_service, test_db, mock_tool, mock_resource, mock_prompt): + """Test successful server registration.""" + # Set up DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + test_db.add = Mock() + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up DB.get to return associated objects + test_db.get = Mock( + side_effect=lambda cls, id: { + (DbTool, 101): mock_tool, + (DbResource, 201): mock_resource, + (DbPrompt, 301): mock_prompt, + }.get((cls, id)) + ) + + # Set up service methods + server_service._notify_server_added = AsyncMock() + server_service._convert_server_to_read = Mock( + return_value=ServerRead( + id=1, + name="test_server", + description="A test server", + icon="server-icon", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + associated_tools=[101], + associated_resources=[201], + associated_prompts=[301], + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ) + + # Create server request + server_create = ServerCreate(name="test_server", description="A test server", icon="server-icon", associated_tools=["101"], associated_resources=["201"], associated_prompts=["301"]) + + # Call method + result = await server_service.register_server(test_db, server_create) + + # Verify DB operations + test_db.add.assert_called_once() + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify notification + server_service._notify_server_added.assert_called_once() + + # Verify result + assert result.name == "test_server" + assert result.description == "A test server" + assert result.icon == "server-icon" + assert result.is_active is True + assert 101 in result.associated_tools + assert 201 in result.associated_resources + assert 301 in result.associated_prompts + + @pytest.mark.asyncio + async def test_register_server_name_conflict(self, server_service, mock_server, test_db): + """Test server registration with name conflict.""" + # Mock DB to return existing server + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_server + test_db.execute = Mock(return_value=mock_scalar) + + # Create server request with conflicting name + server_create = ServerCreate(name="test_server", description="A new server", icon="new-icon") # Same name as mock_server + + # Should raise conflict error + with pytest.raises(ServerNameConflictError) as exc_info: + await server_service.register_server(test_db, server_create) + + assert "Server already exists with name" in str(exc_info.value) + assert exc_info.value.name == "test_server" + assert exc_info.value.is_active == mock_server.is_active + assert exc_info.value.server_id == mock_server.id + + @pytest.mark.asyncio + async def test_register_server_invalid_associated_tool(self, server_service, test_db): + """Test server registration with non-existent associated tool.""" + # Set up DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + # Set up DB.get to return None for tool (not found) + test_db.get = Mock(return_value=None) + test_db.rollback = Mock() + + # Create server request with non-existent tool + server_create = ServerCreate(name="test_server", description="A test server", associated_tools=["999"]) # Non-existent tool ID + + # Should raise error about non-existent tool + with pytest.raises(ServerError) as exc_info: + await server_service.register_server(test_db, server_create) + + assert "Tool with id 999 does not exist" in str(exc_info.value) + + # Verify rollback + test_db.rollback.assert_called_once() + + @pytest.mark.asyncio + async def test_list_servers(self, server_service, mock_server, test_db): + """Test listing servers.""" + # Mock DB to return a list of servers + mock_scalar_result = MagicMock() + mock_scalar_result.all.return_value = [mock_server] + mock_execute = Mock(return_value=mock_scalar_result) + test_db.execute = mock_execute + + # Set up conversion + server_read = ServerRead( + id=1, + name="test_server", + description="A test server", + icon="server-icon", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + associated_tools=[101], + associated_resources=[201], + associated_prompts=[301], + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + server_service._convert_server_to_read = Mock(return_value=server_read) + + # Call method + result = await server_service.list_servers(test_db) + + # Verify DB query + test_db.execute.assert_called_once() + + # Verify result + assert len(result) == 1 + assert result[0] == server_read + server_service._convert_server_to_read.assert_called_once_with(mock_server) + + @pytest.mark.asyncio + async def test_get_server(self, server_service, mock_server, test_db): + """Test getting a server by ID.""" + # Mock DB get to return server + test_db.get = Mock(return_value=mock_server) + + # Set up conversion + server_read = ServerRead( + id=1, + name="test_server", + description="A test server", + icon="server-icon", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + associated_tools=[101], + associated_resources=[201], + associated_prompts=[301], + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + server_service._convert_server_to_read = Mock(return_value=server_read) + + # Call method + result = await server_service.get_server(test_db, 1) + + # Verify DB query + test_db.get.assert_called_once_with(DbServer, 1) + + # Verify result + assert result == server_read + server_service._convert_server_to_read.assert_called_once_with(mock_server) + + @pytest.mark.asyncio + async def test_get_server_not_found(self, server_service, test_db): + """Test getting a non-existent server.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Should raise NotFoundError + with pytest.raises(ServerNotFoundError) as exc_info: + await server_service.get_server(test_db, 999) + + assert "Server not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_update_server(self, server_service, mock_server, test_db, mock_tool, mock_resource, mock_prompt): + """Test updating a server.""" + # Mock DB get to return server + test_db.get = Mock( + side_effect=lambda cls, id: ( + mock_server + if cls == DbServer and id == 1 + else { + (DbTool, 102): mock_tool, + (DbResource, 202): mock_resource, + (DbPrompt, 302): mock_prompt, + }.get((cls, id)) + ) + ) + + # Mock DB to check for name conflicts + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up service methods + server_service._notify_server_updated = AsyncMock() + server_service._convert_server_to_read = Mock( + return_value=ServerRead( + id=1, + name="updated_server", + description="An updated server", + icon="updated-icon", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + associated_tools=[102], + associated_resources=[202], + associated_prompts=[302], + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ) + + # Create update request + server_update = ServerUpdate(name="updated_server", description="An updated server", icon="updated-icon", associated_tools=["102"], associated_resources=["202"], associated_prompts=["302"]) + + # Call method + result = await server_service.update_server(test_db, 1, server_update) + + # Verify DB operations + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify server properties were updated + assert mock_server.name == "updated_server" + assert mock_server.description == "An updated server" + assert mock_server.icon == "updated-icon" + + # Verify notification + server_service._notify_server_updated.assert_called_once() + + # Verify result + assert result.name == "updated_server" + assert result.description == "An updated server" + assert result.icon == "updated-icon" + assert 102 in result.associated_tools + assert 202 in result.associated_resources + assert 302 in result.associated_prompts + + @pytest.mark.asyncio + async def test_update_server_not_found(self, server_service, test_db): + """Test updating a non-existent server.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Create update request + server_update = ServerUpdate(name="updated_server", description="An updated server") + + # Should raise NotFoundError + with pytest.raises(ServerNotFoundError) as exc_info: + await server_service.update_server(test_db, 999, server_update) + + assert "Server not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_update_server_name_conflict(self, server_service, mock_server, test_db): + """Test updating a server with a name that conflicts with another server.""" + # Create a second server (the one being updated) + server1 = mock_server + + # Create a conflicting server + server2 = MagicMock(spec=DbServer) + server2.id = 2 + server2.name = "existing_server" + server2.is_active = True + + # Mock DB get to return server1 + test_db.get = Mock(return_value=server1) + + # Mock DB to check for name conflicts and return server2 + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = server2 + test_db.execute = Mock(return_value=mock_scalar) + + test_db.rollback = Mock() + + # Create update request with conflicting name + server_update = ServerUpdate( + name="existing_server", # Name that conflicts with server2 + ) + + # Should raise conflict error + with pytest.raises(ServerNameConflictError) as exc_info: + await server_service.update_server(test_db, 1, server_update) + + assert "Server already exists with name" in str(exc_info.value) + assert exc_info.value.name == "existing_server" + assert exc_info.value.is_active == server2.is_active + assert exc_info.value.server_id == server2.id + + @pytest.mark.asyncio + async def test_toggle_server_status(self, server_service, mock_server, test_db): + """Test toggling server active status.""" + # Mock DB get to return server + test_db.get = Mock(return_value=mock_server) + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up service methods + server_service._notify_server_activated = AsyncMock() + server_service._notify_server_deactivated = AsyncMock() + server_service._convert_server_to_read = Mock( + return_value=ServerRead( + id=1, + name="test_server", + description="A test server", + icon="server-icon", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=False, # Deactivated + associated_tools=[101], + associated_resources=[201], + associated_prompts=[301], + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ) + + # Deactivate the server (it's active by default) + result = await server_service.toggle_server_status(test_db, 1, activate=False) + + # Verify DB operations + test_db.get.assert_called_once_with(DbServer, 1) + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify properties were updated + assert mock_server.is_active is False + + # Verify notification + server_service._notify_server_deactivated.assert_called_once() + server_service._notify_server_activated.assert_not_called() + + # Verify result + assert result.is_active is False + + @pytest.mark.asyncio + async def test_delete_server(self, server_service, mock_server, test_db): + """Test deleting a server.""" + # Mock DB get to return server + test_db.get = Mock(return_value=mock_server) + test_db.delete = Mock() + test_db.commit = Mock() + + # Set up service methods + server_service._notify_server_deleted = AsyncMock() + + # Call method + await server_service.delete_server(test_db, 1) + + # Verify DB operations + test_db.get.assert_called_once_with(DbServer, 1) + test_db.delete.assert_called_once_with(mock_server) + test_db.commit.assert_called_once() + + # Verify notification + server_service._notify_server_deleted.assert_called_once() + + @pytest.mark.asyncio + async def test_delete_server_not_found(self, server_service, test_db): + """Test deleting a non-existent server.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Should raise NotFoundError + with pytest.raises(ServerNotFoundError) as exc_info: + await server_service.delete_server(test_db, 999) + + assert "Server not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_reset_metrics(self, server_service, test_db): + """Test resetting metrics.""" + # Mock DB operations + test_db.execute = Mock() + test_db.commit = Mock() + + # Call method + await server_service.reset_metrics(test_db) + + # Verify DB operations + test_db.execute.assert_called_once() + test_db.commit.assert_called_once() diff --git a/tests/unit/mcpgateway/services/test_tool_service.py b/tests/unit/mcpgateway/services/test_tool_service.py new file mode 100644 index 0000000000..a4660bb4e4 --- /dev/null +++ b/tests/unit/mcpgateway/services/test_tool_service.py @@ -0,0 +1,670 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for tool service implementation. +""" + +import json +from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch + +import pytest +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError + +from mcpgateway.db import Tool as DbTool +from mcpgateway.schemas import ToolCreate, ToolRead, ToolUpdate +from mcpgateway.services.tool_service import ( + ToolError, + ToolInvocationError, + ToolNameConflictError, + ToolNotFoundError, + ToolService, +) + + +@pytest.fixture +def tool_service(): + """Create a tool service instance.""" + service = ToolService() + service._http_client = AsyncMock() + return service + + +@pytest.fixture +def mock_tool(): + """Create a mock tool model.""" + tool = MagicMock(spec=DbTool) + tool.id = 1 + tool.name = "test_tool" + tool.url = "http://example.com/tools/test" + tool.description = "A test tool" + tool.integration_type = "MCP" + tool.request_type = "POST" + tool.headers = {"Content-Type": "application/json"} + tool.input_schema = {"type": "object", "properties": {"param": {"type": "string"}}} + tool.jsonpath_filter = "" + tool.created_at = "2023-01-01T00:00:00" + tool.updated_at = "2023-01-01T00:00:00" + tool.is_active = True + tool.auth_type = None + tool.auth_username = None + tool.auth_password = None + tool.auth_token = None + tool.gateway_id = None + + # Set up metrics + tool.metrics = [] + tool.execution_count = 0 + tool.successful_executions = 0 + tool.failed_executions = 0 + tool.failure_rate = 0.0 + tool.min_response_time = None + tool.max_response_time = None + tool.avg_response_time = None + tool.last_execution_time = None + tool.metrics_summary = { + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + } + + return tool + + +class TestToolService: + """Tests for the ToolService class.""" + + @pytest.mark.asyncio + async def test_register_tool(self, tool_service, mock_tool, test_db): + """Test successful tool registration.""" + # Set up DB behavior + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + test_db.add = Mock() + test_db.commit = Mock() + test_db.refresh = Mock() + + # Set up tool service methods + tool_service._notify_tool_added = AsyncMock() + tool_service._convert_tool_to_read = Mock( + return_value=ToolRead( + id=1, + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + jsonpath_filter="", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + gateway_id=None, + execution_count=0, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ) + + # Create tool request + tool_create = ToolCreate( + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + ) + + # Call method + result = await tool_service.register_tool(test_db, tool_create) + + # Verify DB operations + test_db.add.assert_called_once() + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify result + assert result.name == "test_tool" + assert result.url == "http://example.com/tools/test" + assert result.integration_type == "MCP" + assert result.is_active is True + + # Verify notification + tool_service._notify_tool_added.assert_called_once() + + @pytest.mark.asyncio + async def test_register_tool_name_conflict(self, tool_service, mock_tool, test_db): + """Test tool registration with name conflict.""" + # Mock DB to return existing tool + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_tool + test_db.execute = Mock(return_value=mock_scalar) + + # Create tool request with conflicting name + tool_create = ToolCreate( + name="test_tool", # Same name as mock_tool + url="http://example.com/tools/new", + description="A new tool", + integration_type="MCP", + request_type="POST", + ) + + # Should raise conflict error + with pytest.raises(ToolNameConflictError) as exc_info: + await tool_service.register_tool(test_db, tool_create) + + assert "Tool already exists with name" in str(exc_info.value) + assert exc_info.value.name == "test_tool" + assert exc_info.value.is_active == mock_tool.is_active + assert exc_info.value.tool_id == mock_tool.id + + @pytest.mark.asyncio + async def test_register_tool_db_integrity_error(self, tool_service, test_db): + """Test tool registration with database IntegrityError.""" + # Mock DB to raise IntegrityError on commit + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + test_db.add = Mock() + test_db.commit = Mock(side_effect=IntegrityError("statement", "params", "orig")) + test_db.rollback = Mock() + + # Create tool request + tool_create = ToolCreate( + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + ) + + # Should raise ToolError + with pytest.raises(ToolError) as exc_info: + await tool_service.register_tool(test_db, tool_create) + + assert "Tool already exists" in str(exc_info.value) + test_db.rollback.assert_called_once() + + @pytest.mark.asyncio + async def test_list_tools(self, tool_service, mock_tool, test_db): + """Test listing tools.""" + # Mock DB to return a list of tools + mock_scalar_result = MagicMock() + mock_scalar_result.all.return_value = [mock_tool] + mock_execute = Mock(return_value=mock_scalar_result) + test_db.execute = mock_execute + + # Mock conversion + tool_read = ToolRead( + id=1, + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + jsonpath_filter="", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + gateway_id=None, + execution_count=0, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + tool_service._convert_tool_to_read = Mock(return_value=tool_read) + + # Call method + result = await tool_service.list_tools(test_db) + + # Verify DB query + test_db.execute.assert_called_once() + + # Verify result + assert len(result) == 1 + assert result[0] == tool_read + tool_service._convert_tool_to_read.assert_called_once_with(mock_tool) + + @pytest.mark.asyncio + async def test_get_tool(self, tool_service, mock_tool, test_db): + """Test getting a tool by ID.""" + # Mock DB get to return tool + test_db.get = Mock(return_value=mock_tool) + + # Mock conversion + tool_read = ToolRead( + id=1, + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + jsonpath_filter="", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + gateway_id=None, + execution_count=0, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + tool_service._convert_tool_to_read = Mock(return_value=tool_read) + + # Call method + result = await tool_service.get_tool(test_db, 1) + + # Verify DB query + test_db.get.assert_called_once_with(DbTool, 1) + + # Verify result + assert result == tool_read + tool_service._convert_tool_to_read.assert_called_once_with(mock_tool) + + @pytest.mark.asyncio + async def test_get_tool_not_found(self, tool_service, test_db): + """Test getting a non-existent tool.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Should raise NotFoundError + with pytest.raises(ToolNotFoundError) as exc_info: + await tool_service.get_tool(test_db, 999) + + assert "Tool not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_delete_tool(self, tool_service, mock_tool, test_db): + """Test deleting a tool.""" + # Mock DB get to return tool + test_db.get = Mock(return_value=mock_tool) + test_db.delete = Mock() + test_db.commit = Mock() + + # Mock notification + tool_service._notify_tool_deleted = AsyncMock() + + # Call method + await tool_service.delete_tool(test_db, 1) + + # Verify DB operations + test_db.get.assert_called_once_with(DbTool, 1) + test_db.delete.assert_called_once_with(mock_tool) + test_db.commit.assert_called_once() + + # Verify notification + tool_service._notify_tool_deleted.assert_called_once() + + @pytest.mark.asyncio + async def test_delete_tool_not_found(self, tool_service, test_db): + """Test deleting a non-existent tool.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Should raise NotFoundError + with pytest.raises(ToolNotFoundError) as exc_info: + await tool_service.delete_tool(test_db, 999) + + assert "Tool not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_toggle_tool_status(self, tool_service, mock_tool, test_db): + """Test toggling tool active status.""" + # Mock DB get to return tool + test_db.get = Mock(return_value=mock_tool) + test_db.commit = Mock() + test_db.refresh = Mock() + + # Mock notification methods + tool_service._notify_tool_activated = AsyncMock() + tool_service._notify_tool_deactivated = AsyncMock() + + # Mock conversion + tool_read = ToolRead( + id=1, + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + jsonpath_filter="", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=False, # Changed to False + gateway_id=None, + execution_count=0, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + tool_service._convert_tool_to_read = Mock(return_value=tool_read) + + # Deactivate the tool (it's active by default) + result = await tool_service.toggle_tool_status(test_db, 1, activate=False) + + # Verify DB operations + test_db.get.assert_called_once_with(DbTool, 1) + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify properties were updated + assert mock_tool.is_active is False + + # Verify notification + tool_service._notify_tool_deactivated.assert_called_once() + tool_service._notify_tool_activated.assert_not_called() + + # Verify result + assert result == tool_read + + @pytest.mark.asyncio + async def test_update_tool(self, tool_service, mock_tool, test_db): + """Test updating a tool.""" + # Mock DB get to return tool + test_db.get = Mock(return_value=mock_tool) + + # Mock DB query to check for name conflicts (returns None = no conflict) + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + test_db.commit = Mock() + test_db.refresh = Mock() + + # Mock notification + tool_service._notify_tool_updated = AsyncMock() + + # Mock conversion + tool_read = ToolRead( + id=1, + name="updated_tool", # Updated name + url="http://example.com/tools/updated", # Updated URL + description="An updated test tool", # Updated description + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + jsonpath_filter="", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + gateway_id=None, + execution_count=0, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + tool_service._convert_tool_to_read = Mock(return_value=tool_read) + + # Create update request + tool_update = ToolUpdate( + name="updated_tool", + url="http://example.com/tools/updated", + description="An updated test tool", + ) + + # Call method + result = await tool_service.update_tool(test_db, 1, tool_update) + + # Verify DB operations + test_db.get.assert_called_once_with(DbTool, 1) + test_db.commit.assert_called_once() + test_db.refresh.assert_called_once() + + # Verify properties were updated + assert mock_tool.name == "updated_tool" + assert mock_tool.url == "http://example.com/tools/updated" + assert mock_tool.description == "An updated test tool" + + # Verify notification + tool_service._notify_tool_updated.assert_called_once() + + # Verify result + assert result == tool_read + + @pytest.mark.asyncio + async def test_update_tool_name_conflict(self, tool_service, mock_tool, test_db): + """Test updating a tool with a name that conflicts with another tool.""" + # Mock DB get to return our tool + test_db.get = Mock(return_value=mock_tool) + + # Create a conflicting tool + conflicting_tool = MagicMock(spec=DbTool) + conflicting_tool.id = 2 + conflicting_tool.name = "existing_tool" + conflicting_tool.is_active = True + + # Mock DB query to check for name conflicts (returns the conflicting tool) + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = conflicting_tool + test_db.execute = Mock(return_value=mock_scalar) + + test_db.rollback = Mock() + + # Create update request with conflicting name + tool_update = ToolUpdate( + name="existing_tool", # Name that conflicts with another tool + ) + + # Should raise conflict error + with pytest.raises(ToolNameConflictError) as exc_info: + await tool_service.update_tool(test_db, 1, tool_update) + + assert "Tool already exists with name" in str(exc_info.value) + assert exc_info.value.name == "existing_tool" + assert exc_info.value.is_active == conflicting_tool.is_active + assert exc_info.value.tool_id == conflicting_tool.id + + @pytest.mark.asyncio + async def test_update_tool_not_found(self, tool_service, test_db): + """Test updating a non-existent tool.""" + # Mock DB get to return None + test_db.get = Mock(return_value=None) + + # Create update request + tool_update = ToolUpdate( + name="updated_tool", + ) + + # Should raise NotFoundError + with pytest.raises(ToolNotFoundError) as exc_info: + await tool_service.update_tool(test_db, 999, tool_update) + + assert "Tool not found: 999" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_invoke_tool_not_found(self, tool_service, test_db): + """Test invoking a non-existent tool.""" + # Mock DB to return no tool + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = None + test_db.execute = Mock(return_value=mock_scalar) + + # Should raise NotFoundError + with pytest.raises(ToolNotFoundError) as exc_info: + await tool_service.invoke_tool(test_db, "nonexistent_tool", {}) + + assert "Tool not found: nonexistent_tool" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_invoke_tool_inactive(self, tool_service, mock_tool, test_db): + """Test invoking an inactive tool.""" + # Set tool to inactive + mock_tool.is_active = False + + # Mock DB to return inactive tool for first query, None for second query + mock_scalar1 = Mock() + mock_scalar1.scalar_one_or_none.return_value = None + + mock_scalar2 = Mock() + mock_scalar2.scalar_one_or_none.return_value = mock_tool + + test_db.execute = Mock(side_effect=[mock_scalar1, mock_scalar2]) + + # Should raise NotFoundError with "inactive" message + with pytest.raises(ToolNotFoundError) as exc_info: + await tool_service.invoke_tool(test_db, "test_tool", {}) + + assert "Tool 'test_tool' exists but is inactive" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_invoke_tool_rest(self, tool_service, mock_tool, test_db): + """Test invoking a REST tool.""" + # Configure tool as REST + mock_tool.integration_type = "REST" + mock_tool.request_type = "POST" + mock_tool.jsonpath_filter = "" + + # Mock DB to return the tool + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_tool + test_db.execute = Mock(return_value=mock_scalar) + + # Mock HTTP client response + mock_response = AsyncMock() + mock_response.raise_for_status = AsyncMock() + mock_response.json.return_value = {"result": {"content": [{"type": "text", "text": "REST tool response"}]}} + tool_service._http_client.request.return_value = mock_response + + # Mock metrics recording + tool_service._record_tool_metric = AsyncMock() + + # Invoke tool + result = await tool_service.invoke_tool(test_db, "test_tool", {"param": "value"}) + + # Verify HTTP request + tool_service._http_client.request.assert_called_once_with( + "POST", + mock_tool.url, + json={ + "jsonrpc": "2.0", + "method": "test_tool", + "params": {"param": "value"}, + "id": 1, + }, + headers=mock_tool.headers, + ) + + # Verify result + assert any(content.text == "REST tool response" for content in result.content) + + # Verify metrics recorded + tool_service._record_tool_metric.assert_called_once_with( + test_db, + mock_tool, + ANY, # Start time + True, # Success + None, # No error + ) + + @pytest.mark.asyncio + async def test_invoke_tool_error(self, tool_service, mock_tool, test_db): + """Test invoking a tool that returns an error.""" + # Configure tool + mock_tool.integration_type = "REST" + mock_tool.request_type = "POST" + + # Mock DB to return the tool + mock_scalar = Mock() + mock_scalar.scalar_one_or_none.return_value = mock_tool + test_db.execute = Mock(return_value=mock_scalar) + + # Mock HTTP client to raise an error + tool_service._http_client.request.side_effect = Exception("HTTP error") + + # Mock metrics recording + tool_service._record_tool_metric = AsyncMock() + + # Should raise ToolInvocationError + with pytest.raises(ToolInvocationError) as exc_info: + await tool_service.invoke_tool(test_db, "test_tool", {"param": "value"}) + + assert "Tool invocation failed: HTTP error" in str(exc_info.value) + + # Verify metrics recorded with error + tool_service._record_tool_metric.assert_called_once_with( + test_db, + mock_tool, + ANY, # Start time + False, # Failed + "HTTP error", # Error message + ) + + @pytest.mark.asyncio + async def test_reset_metrics(self, tool_service, test_db): + """Test resetting metrics.""" + # Mock DB operations + test_db.execute = Mock() + test_db.commit = Mock() + + # Reset all metrics + await tool_service.reset_metrics(test_db) + + # Verify DB operations + test_db.execute.assert_called_once() + test_db.commit.assert_called_once() + + # Reset metrics for specific tool + test_db.execute.reset_mock() + test_db.commit.reset_mock() + + await tool_service.reset_metrics(test_db, tool_id=1) + + # Verify DB operations with tool_id + test_db.execute.assert_called_once() + test_db.commit.assert_called_once() diff --git a/tests/unit/mcpgateway/test_admin.py b/tests/unit/mcpgateway/test_admin.py new file mode 100644 index 0000000000..cf95fe63f8 --- /dev/null +++ b/tests/unit/mcpgateway/test_admin.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the admin module. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module tests the admin UI routes for the MCP Gateway, ensuring +they properly handle server, tool, resource, prompt, gateway and root management. +""" + +import json +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from fastapi import HTTPException, Request +from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse +from sqlalchemy.orm import Session + +from mcpgateway.admin import ( + GatewayCreate, + GatewayUpdate, + PromptCreate, + PromptUpdate, + ResourceCreate, + ResourceUpdate, + ServerCreate, + ServerUpdate, + ToolCreate, + ToolUpdate, + admin_add_gateway, + admin_add_prompt, + admin_add_resource, + admin_add_root, + admin_add_server, + admin_add_tool, + admin_delete_gateway, + admin_delete_prompt, + admin_delete_resource, + admin_delete_root, + admin_delete_server, + admin_delete_tool, + admin_edit_gateway, + admin_edit_prompt, + admin_edit_resource, + admin_edit_server, + admin_edit_tool, + admin_get_gateway, + admin_get_metrics, + admin_get_prompt, + admin_get_resource, + admin_get_server, + admin_get_tool, + admin_list_gateways, + admin_list_prompts, + admin_list_resources, + admin_list_servers, + admin_list_tools, + admin_reset_metrics, + admin_toggle_gateway, + admin_toggle_prompt, + admin_toggle_resource, + admin_toggle_server, + admin_toggle_tool, + admin_ui, +) +from mcpgateway.services.gateway_service import GatewayService +from mcpgateway.services.prompt_service import PromptService +from mcpgateway.services.resource_service import ResourceService +from mcpgateway.services.root_service import RootService +from mcpgateway.services.server_service import ServerNotFoundError, ServerService +from mcpgateway.services.tool_service import ( + ToolError, + ToolNameConflictError, + ToolService, +) + + +@pytest.fixture +def mock_db(): + """Create a mock database session.""" + return MagicMock(spec=Session) + + +@pytest.fixture +def mock_request(): + """Create a mock FastAPI request.""" + request = MagicMock(spec=Request) + request.form = AsyncMock( + return_value={ + "name": "test-name", + "url": "http://example.com", + "description": "Test description", + "icon": "http://example.com/icon.png", + "uri": "/test/resource", + "mimeType": "text/plain", + "template": "Template content", + "content": "Test content", + "associatedTools": "1,2,3", + "associatedResources": "4,5", + "associatedPrompts": "6", + "requestType": "POST", + "integrationType": "MCP", + "headers": "{}", + "input_schema": "{}", + "jsonpath_filter": "$.", + "auth_type": "", + "auth_username": "", + "auth_password": "", + "auth_token": "", + "auth_header_key": "", + "auth_header_value": "", + "arguments": "[]", + "activate": "true", + } + ) + request.app.state.templates = MagicMock() + request.app.state.templates.TemplateResponse.return_value = HTMLResponse(content="") + request.query_params = {"include_inactive": "false"} + return request + + +class TestAdminServerRoutes: + """Test admin routes for server management.""" + + @patch.object(ServerService, "list_servers") + async def test_admin_list_servers(self, mock_list_servers, mock_db): + """Test listing servers through admin UI.""" + # Setup + mock_server1 = MagicMock() + mock_server1.dict.return_value = {"id": 1, "name": "Server 1"} + mock_server2 = MagicMock() + mock_server2.dict.return_value = {"id": 2, "name": "Server 2"} + mock_list_servers.return_value = [mock_server1, mock_server2] + + # Execute + result = await admin_list_servers(False, mock_db, "test-user") + + # Assert + mock_list_servers.assert_called_once_with(mock_db, include_inactive=False) + assert len(result) == 2 + assert result[0] == {"id": 1, "name": "Server 1"} + assert result[1] == {"id": 2, "name": "Server 2"} + + @patch.object(ServerService, "get_server") + async def test_admin_get_server(self, mock_get_server, mock_db): + """Test getting a single server through admin UI.""" + # Setup + mock_server = MagicMock() + mock_server.dict.return_value = {"id": 1, "name": "Server 1"} + mock_get_server.return_value = mock_server + + # Execute + result = await admin_get_server(1, mock_db, "test-user") + + # Assert + mock_get_server.assert_called_once_with(mock_db, 1) + assert result == {"id": 1, "name": "Server 1"} + + @patch.object(ServerService, "get_server") + async def test_admin_get_server_not_found(self, mock_get_server, mock_db): + """Test getting a non-existent server through admin UI.""" + # Setup + mock_get_server.side_effect = ServerNotFoundError("Server not found") + + # Execute and Assert + with pytest.raises(HTTPException) as excinfo: + await admin_get_server(999, mock_db, "test-user") + + assert excinfo.value.status_code == 404 + assert "Server not found" in str(excinfo.value.detail) + + @patch.object(ServerService, "register_server") + async def test_admin_add_server(self, mock_register_server, mock_request, mock_db): + """Test adding a server through admin UI.""" + # Execute + result = await admin_add_server(mock_request, mock_db, "test-user") + + # Assert + mock_register_server.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#catalog" in result.headers["location"] + + @patch.object(ServerService, "update_server") + async def test_admin_edit_server(self, mock_update_server, mock_request, mock_db): + """Test editing a server through admin UI.""" + # Execute + result = await admin_edit_server(1, mock_request, mock_db, "test-user") + + # Assert + mock_update_server.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#catalog" in result.headers["location"] + + @patch.object(ServerService, "toggle_server_status") + async def test_admin_toggle_server(self, mock_toggle_status, mock_request, mock_db): + """Test toggling server status through admin UI.""" + # Execute + result = await admin_toggle_server(1, mock_request, mock_db, "test-user") + + # Assert + mock_toggle_status.assert_called_once_with(mock_db, 1, True) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#catalog" in result.headers["location"] + + @patch.object(ServerService, "delete_server") + async def test_admin_delete_server(self, mock_delete_server, mock_db): + """Test deleting a server through admin UI.""" + # Execute + result = await admin_delete_server(1, mock_db, "test-user") + + # Assert + mock_delete_server.assert_called_once_with(mock_db, 1) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#catalog" in result.headers["location"] + + +class TestAdminToolRoutes: + """Test admin routes for tool management.""" + + @patch.object(ToolService, "list_tools") + async def test_admin_list_tools(self, mock_list_tools, mock_db): + """Test listing tools through admin UI.""" + # Setup + mock_tool1 = MagicMock() + mock_tool1.dict.return_value = {"id": 1, "name": "Tool 1"} + mock_tool2 = MagicMock() + mock_tool2.dict.return_value = {"id": 2, "name": "Tool 2"} + mock_list_tools.return_value = [mock_tool1, mock_tool2] + + # Execute + result = await admin_list_tools(False, mock_db, "test-user") + + # Assert + mock_list_tools.assert_called_once_with(mock_db, include_inactive=False) + assert len(result) == 2 + assert result[0] == {"id": 1, "name": "Tool 1"} + assert result[1] == {"id": 2, "name": "Tool 2"} + + @patch.object(ToolService, "get_tool") + async def test_admin_get_tool(self, mock_get_tool, mock_db): + """Test getting a single tool through admin UI.""" + # Setup + mock_tool = MagicMock() + mock_tool.dict.return_value = {"id": 1, "name": "Tool 1"} + mock_get_tool.return_value = mock_tool + + # Execute + result = await admin_get_tool(1, mock_db, "test-user") + + # Assert + mock_get_tool.assert_called_once_with(mock_db, 1) + assert result == {"id": 1, "name": "Tool 1"} + + @patch.object(ToolService, "register_tool") + async def test_admin_add_tool(self, mock_register_tool, mock_request, mock_db): + """Test adding a tool through admin UI.""" + # Execute + result = await admin_add_tool(mock_request, mock_db, "test-user") + + # Assert + mock_register_tool.assert_called_once() + assert isinstance(result, JSONResponse) + assert result.status_code == 200 + assert json.loads(result.body)["success"] is True + + @patch.object(ToolService, "register_tool") + async def test_admin_add_tool_conflict(self, mock_register_tool, mock_request, mock_db): + """Test adding a tool with a conflicting name.""" + # Setup + mock_register_tool.side_effect = ToolNameConflictError("Tool name exists") + + # Execute + result = await admin_add_tool(mock_request, mock_db, "test-user") + + # Assert + assert isinstance(result, JSONResponse) + assert result.status_code == 400 + assert json.loads(result.body)["success"] is False + + @patch.object(ToolService, "update_tool") + async def test_admin_edit_tool(self, mock_update_tool, mock_request, mock_db): + """Test editing a tool through admin UI.""" + # Execute + result = await admin_edit_tool(1, mock_request, mock_db, "test-user") + + # Assert + mock_update_tool.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#tools" in result.headers["location"] + + @patch.object(ToolService, "update_tool") + async def test_admin_edit_tool_conflict(self, mock_update_tool, mock_request, mock_db): + """Test editing a tool with a conflicting name.""" + # Setup + mock_update_tool.side_effect = ToolNameConflictError("Tool name exists") + + # Execute + result = await admin_edit_tool(1, mock_request, mock_db, "test-user") + + # Assert + assert isinstance(result, JSONResponse) + assert result.status_code == 400 + assert json.loads(result.body)["success"] is False + + @patch.object(ToolService, "toggle_tool_status") + async def test_admin_toggle_tool(self, mock_toggle_status, mock_request, mock_db): + """Test toggling tool status through admin UI.""" + # Execute + result = await admin_toggle_tool(1, mock_request, mock_db, "test-user") + + # Assert + mock_toggle_status.assert_called_once_with(mock_db, 1, True) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#tools" in result.headers["location"] + + @patch.object(ToolService, "delete_tool") + async def test_admin_delete_tool(self, mock_delete_tool, mock_db): + """Test deleting a tool through admin UI.""" + # Execute + result = await admin_delete_tool(1, mock_db, "test-user") + + # Assert + mock_delete_tool.assert_called_once_with(mock_db, 1) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#tools" in result.headers["location"] + + +class TestAdminResourceRoutes: + """Test admin routes for resource management.""" + + @patch.object(ResourceService, "list_resources") + async def test_admin_list_resources(self, mock_list_resources, mock_db): + """Test listing resources through admin UI.""" + # Setup + mock_resource1 = MagicMock() + mock_resource1.dict.return_value = {"id": 1, "name": "Resource 1"} + mock_resource2 = MagicMock() + mock_resource2.dict.return_value = {"id": 2, "name": "Resource 2"} + mock_list_resources.return_value = [mock_resource1, mock_resource2] + + # Execute + result = await admin_list_resources(False, mock_db, "test-user") + + # Assert + mock_list_resources.assert_called_once_with(mock_db, include_inactive=False) + assert len(result) == 2 + assert result[0] == {"id": 1, "name": "Resource 1"} + assert result[1] == {"id": 2, "name": "Resource 2"} + + @patch.object(ResourceService, "get_resource_by_uri") + @patch.object(ResourceService, "read_resource") + async def test_admin_get_resource(self, mock_read_resource, mock_get_resource, mock_db): + """Test getting a single resource through admin UI.""" + # Setup + mock_resource = MagicMock() + mock_resource.dict.return_value = {"id": 1, "name": "Resource 1"} + mock_get_resource.return_value = mock_resource + mock_read_resource.return_value = {"type": "resource", "text": "content"} + + # Execute + result = await admin_get_resource("/test/resource", mock_db, "test-user") + + # Assert + mock_get_resource.assert_called_once_with(mock_db, "/test/resource") + mock_read_resource.assert_called_once_with(mock_db, "/test/resource") + assert result["resource"] == {"id": 1, "name": "Resource 1"} + assert result["content"] == {"type": "resource", "text": "content"} + + @patch.object(ResourceService, "register_resource") + async def test_admin_add_resource(self, mock_register_resource, mock_request, mock_db): + """Test adding a resource through admin UI.""" + # Execute + result = await admin_add_resource(mock_request, mock_db, "test-user") + + # Assert + mock_register_resource.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#resources" in result.headers["location"] + + @patch.object(ResourceService, "update_resource") + async def test_admin_edit_resource(self, mock_update_resource, mock_request, mock_db): + """Test editing a resource through admin UI.""" + # Execute + result = await admin_edit_resource("/test/resource", mock_request, mock_db, "test-user") + + # Assert + mock_update_resource.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#resources" in result.headers["location"] + + @patch.object(ResourceService, "toggle_resource_status") + async def test_admin_toggle_resource(self, mock_toggle_status, mock_request, mock_db): + """Test toggling resource status through admin UI.""" + # Execute + result = await admin_toggle_resource(1, mock_request, mock_db, "test-user") + + # Assert + mock_toggle_status.assert_called_once_with(mock_db, 1, True) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#resources" in result.headers["location"] + + @patch.object(ResourceService, "delete_resource") + async def test_admin_delete_resource(self, mock_delete_resource, mock_db): + """Test deleting a resource through admin UI.""" + # Execute + result = await admin_delete_resource("/test/resource", mock_db, "test-user") + + # Assert + mock_delete_resource.assert_called_once_with(mock_db, "/test/resource") + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#resources" in result.headers["location"] + + +class TestAdminPromptRoutes: + """Test admin routes for prompt management.""" + + @patch.object(PromptService, "list_prompts") + async def test_admin_list_prompts(self, mock_list_prompts, mock_db): + """Test listing prompts through admin UI.""" + # Setup + mock_prompt1 = MagicMock() + mock_prompt1.dict.return_value = {"id": 1, "name": "Prompt 1"} + mock_prompt2 = MagicMock() + mock_prompt2.dict.return_value = {"id": 2, "name": "Prompt 2"} + mock_list_prompts.return_value = [mock_prompt1, mock_prompt2] + + # Execute + result = await admin_list_prompts(False, mock_db, "test-user") + + # Assert + mock_list_prompts.assert_called_once_with(mock_db, include_inactive=False) + assert len(result) == 2 + assert result[0] == {"id": 1, "name": "Prompt 1"} + assert result[1] == {"id": 2, "name": "Prompt 2"} + + @patch.object(PromptService, "get_prompt_details") + async def test_admin_get_prompt(self, mock_get_prompt_details, mock_db): + """Test getting a single prompt through admin UI.""" + # Setup + mock_get_prompt_details.return_value = { + "id": 1, + "name": "Prompt 1", + "template": "Example template", + "description": "Test prompt", + "arguments": [], + "created_at": "2023-01-01T00:00:00", + "updated_at": "2023-01-01T00:00:00", + "is_active": True, + "metrics": { + "total_executions": 10, + "successful_executions": 9, + "failed_executions": 1, + "failure_rate": 0.1, + "min_response_time": 0.1, + "max_response_time": 0.5, + "avg_response_time": 0.2, + "last_execution_time": "2023-01-01T00:00:00", + }, + } + + # Execute + result = await admin_get_prompt("test-prompt", mock_db, "test-user") + + # Assert + mock_get_prompt_details.assert_called_once_with(mock_db, "test-prompt") + assert "id" in result + assert result["name"] == "Prompt 1" + + @patch.object(PromptService, "register_prompt") + async def test_admin_add_prompt(self, mock_register_prompt, mock_request, mock_db): + """Test adding a prompt through admin UI.""" + # Execute + result = await admin_add_prompt(mock_request, mock_db, "test-user") + + # Assert + mock_register_prompt.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#prompts" in result.headers["location"] + + @patch.object(PromptService, "update_prompt") + async def test_admin_edit_prompt(self, mock_update_prompt, mock_request, mock_db): + """Test editing a prompt through admin UI.""" + # Execute + result = await admin_edit_prompt("test-prompt", mock_request, mock_db, "test-user") + + # Assert + mock_update_prompt.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#prompts" in result.headers["location"] + + @patch.object(PromptService, "toggle_prompt_status") + async def test_admin_toggle_prompt(self, mock_toggle_status, mock_request, mock_db): + """Test toggling prompt status through admin UI.""" + # Execute + result = await admin_toggle_prompt(1, mock_request, mock_db, "test-user") + + # Assert + mock_toggle_status.assert_called_once_with(mock_db, 1, True) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#prompts" in result.headers["location"] + + @patch.object(PromptService, "delete_prompt") + async def test_admin_delete_prompt(self, mock_delete_prompt, mock_db): + """Test deleting a prompt through admin UI.""" + # Execute + result = await admin_delete_prompt("test-prompt", mock_db, "test-user") + + # Assert + mock_delete_prompt.assert_called_once_with(mock_db, "test-prompt") + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#prompts" in result.headers["location"] + + +class TestAdminGatewayRoutes: + """Test admin routes for gateway management.""" + + @patch.object(GatewayService, "list_gateways") + async def test_admin_list_gateways(self, mock_list_gateways, mock_db): + """Test listing gateways through admin UI.""" + # Setup + mock_gateway1 = MagicMock() + mock_gateway1.dict.return_value = {"id": 1, "name": "Gateway 1"} + mock_gateway2 = MagicMock() + mock_gateway2.dict.return_value = {"id": 2, "name": "Gateway 2"} + mock_list_gateways.return_value = [mock_gateway1, mock_gateway2] + + # Execute + result = await admin_list_gateways(False, mock_db, "test-user") + + # Assert + mock_list_gateways.assert_called_once_with(mock_db, include_inactive=False) + assert len(result) == 2 + assert result[0] == {"id": 1, "name": "Gateway 1"} + assert result[1] == {"id": 2, "name": "Gateway 2"} + + @patch.object(GatewayService, "get_gateway") + async def test_admin_get_gateway(self, mock_get_gateway, mock_db): + """Test getting a single gateway through admin UI.""" + # Setup + mock_gateway = MagicMock() + mock_gateway.dict.return_value = {"id": 1, "name": "Gateway 1"} + mock_get_gateway.return_value = mock_gateway + + # Execute + result = await admin_get_gateway(1, mock_db, "test-user") + + # Assert + mock_get_gateway.assert_called_once_with(mock_db, 1) + assert result == {"id": 1, "name": "Gateway 1"} + + @patch.object(GatewayService, "register_gateway") + async def test_admin_add_gateway(self, mock_register_gateway, mock_request, mock_db): + """Test adding a gateway through admin UI.""" + # Execute + result = await admin_add_gateway(mock_request, mock_db, "test-user") + + # Assert + mock_register_gateway.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#gateways" in result.headers["location"] + + @patch.object(GatewayService, "update_gateway") + async def test_admin_edit_gateway(self, mock_update_gateway, mock_request, mock_db): + """Test editing a gateway through admin UI.""" + # Execute + result = await admin_edit_gateway(1, mock_request, mock_db, "test-user") + + # Assert + mock_update_gateway.assert_called_once() + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#gateways" in result.headers["location"] + + @patch.object(GatewayService, "toggle_gateway_status") + async def test_admin_toggle_gateway(self, mock_toggle_status, mock_request, mock_db): + """Test toggling gateway status through admin UI.""" + # Execute + result = await admin_toggle_gateway(1, mock_request, mock_db, "test-user") + + # Assert + mock_toggle_status.assert_called_once_with(mock_db, 1, True) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#gateways" in result.headers["location"] + + @patch.object(GatewayService, "delete_gateway") + async def test_admin_delete_gateway(self, mock_delete_gateway, mock_db): + """Test deleting a gateway through admin UI.""" + # Execute + result = await admin_delete_gateway(1, mock_db, "test-user") + + # Assert + mock_delete_gateway.assert_called_once_with(mock_db, 1) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#gateways" in result.headers["location"] + + +class TestAdminRootRoutes: + """Test admin routes for root management.""" + + @patch.object(RootService, "add_root") + async def test_admin_add_root(self, mock_add_root, mock_request, mock_db): + """Test adding a root through admin UI.""" + # Execute + result = await admin_add_root(mock_request, mock_db, "test-user") + + # Assert + mock_add_root.assert_called_once_with("/test/resource", None) + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#roots" in result.headers["location"] + + @patch.object(RootService, "remove_root") + async def test_admin_delete_root(self, mock_remove_root, mock_db): + """Test deleting a root through admin UI.""" + # Execute + result = await admin_delete_root("/test/root", mock_db, "test-user") + + # Assert + mock_remove_root.assert_called_once_with("/test/root") + assert isinstance(result, RedirectResponse) + assert result.status_code == 303 + assert "/admin#roots" in result.headers["location"] + + +class TestAdminMetricsRoutes: + """Test admin routes for metrics management.""" + + @patch.multiple( + ToolService, + aggregate_metrics=AsyncMock(return_value={"total_executions": 100}), + ) + @patch.multiple( + ResourceService, + aggregate_metrics=AsyncMock(return_value={"total_executions": 50}), + ) + @patch.multiple( + ServerService, + aggregate_metrics=AsyncMock(return_value={"total_executions": 30}), + ) + @patch.multiple( + PromptService, + aggregate_metrics=AsyncMock(return_value={"total_executions": 20}), + ) + async def test_admin_get_metrics(self, mock_db): + """Test getting metrics through admin UI.""" + # Execute + result = await admin_get_metrics(mock_db, "test-user") + + # Assert + assert "tools" in result + assert "resources" in result + assert "servers" in result + assert "prompts" in result + assert result["tools"]["total_executions"] == 100 + assert result["resources"]["total_executions"] == 50 + assert result["servers"]["total_executions"] == 30 + assert result["prompts"]["total_executions"] == 20 + + @patch.multiple( + ToolService, + reset_metrics=AsyncMock(), + ) + @patch.multiple( + ResourceService, + reset_metrics=AsyncMock(), + ) + @patch.multiple( + ServerService, + reset_metrics=AsyncMock(), + ) + @patch.multiple( + PromptService, + reset_metrics=AsyncMock(), + ) + async def test_admin_reset_metrics(self, mock_db): + """Test resetting metrics through admin UI.""" + # Execute + result = await admin_reset_metrics(mock_db, "test-user") + + # Assert + assert result["message"] == "All metrics reset successfully" + assert result["success"] is True + ToolService.reset_metrics.assert_called_once() + ResourceService.reset_metrics.assert_called_once() + ServerService.reset_metrics.assert_called_once() + PromptService.reset_metrics.assert_called_once() diff --git a/tests/unit/mcpgateway/test_config.py b/tests/unit/mcpgateway/test_config.py new file mode 100644 index 0000000000..109ae83230 --- /dev/null +++ b/tests/unit/mcpgateway/test_config.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +from unittest.mock import patch + +import pytest + +from mcpgateway.config import Settings, get_settings + + +def test_settings_default_values(): + """Test that Settings has expected default values.""" + settings = Settings() + assert settings.app_name == "MCP Gateway" + assert settings.host == "0.0.0.0" + assert settings.port == 4444 + assert settings.database_url == "sqlite:///./mcp.db" + assert settings.basic_auth_user == "admin" + assert settings.basic_auth_password == "changeme" + assert settings.auth_required is True + + +def test_api_key_property(): + """Test the api_key property.""" + settings = Settings(basic_auth_user="test_user", basic_auth_password="test_pass") + assert settings.api_key == "test_user:test_pass" + + +def test_supports_transport_properties(): + """Test the transport support properties.""" + # Test 'all' transport type + settings = Settings(transport_type="all") + assert settings.supports_http is True + assert settings.supports_websocket is True + assert settings.supports_sse is True + + # Test 'http' transport type + settings = Settings(transport_type="http") + assert settings.supports_http is True + assert settings.supports_websocket is False + assert settings.supports_sse is False + + # Test 'ws' transport type + settings = Settings(transport_type="ws") + assert settings.supports_http is False + assert settings.supports_websocket is True + assert settings.supports_sse is False + + +@patch("mcpgateway.config.Settings") +def test_get_settings_caching(mock_settings): + """Test that get_settings caches the result.""" + mock_settings.return_value = "test_settings" + + # First call should create settings + result1 = get_settings() + assert result1 == "test_settings" + + # Second call should use cached value + mock_settings.return_value = "new_settings" + result2 = get_settings() + assert result2 == "test_settings" # Should still be the first value + + # Settings should only be created once + assert mock_settings.call_count == 1 diff --git a/tests/unit/mcpgateway/test_main.py b/tests/unit/mcpgateway/test_main.py new file mode 100644 index 0000000000..38f9a12979 --- /dev/null +++ b/tests/unit/mcpgateway/test_main.py @@ -0,0 +1,342 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for the main API endpoints. +""" + +import json +import os +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from mcpgateway.config import settings +from mcpgateway.schemas import ResourceRead, ServerRead, ToolRead +from mcpgateway.types import InitializeResult, ResourceContent, ServerCapabilities + +PROTOCOL_VERSION = os.getenv("PROTOCOL_VERSION", "2025-03-26") + + +@pytest.fixture +def test_client(app): + """Create a test client for the app.""" + return TestClient(app) + + +@pytest.fixture +def mock_jwt_token(): + """Create a mock JWT token.""" + return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0X3VzZXIiLCJleHAiOjk5OTk5OTk5OTl9.this_is_a_test_token" + + +@pytest.fixture +def auth_headers(mock_jwt_token): + """Create headers with authentication.""" + return {"Authorization": f"Bearer {mock_jwt_token}"} + + +class TestAPIEndpoints: + """Tests for the API endpoints.""" + + def test_health_check(self, test_client): + """Test the health check endpoint.""" + response = test_client.get("/health") + assert response.status_code == 200 + assert response.json()["status"] == "healthy" + + def test_root_redirect(self, test_client): + """Test root path redirects to admin.""" + response = test_client.get("/", allow_redirects=False) + assert response.status_code == 307 + assert response.headers["location"] == "/admin" + + def test_static_files(self, test_client): + """Test static files are served.""" + with patch("os.path.exists", return_value=True): + with patch("builtins.open", MagicMock()): + response = test_client.get("/static/test.css") + # FastAPI will return 404 since the file doesn't actually exist in test + # We just want to verify the route is registered + assert response.status_code == 404 + + @patch("mcpgateway.main.validate_request") + @patch("mcpgateway.main.handle_initialize_logic") + def test_initialize_endpoint(self, mock_handle_initialize, mock_validate, test_client, auth_headers): + """Test the initialize endpoint.""" + # Set up mocks + mock_capabilities = ServerCapabilities( + prompts={"listChanged": True}, + resources={"subscribe": True, "listChanged": True}, + tools={"listChanged": True}, + logging={}, + roots={"listChanged": True}, + sampling={}, + ) + mock_result = InitializeResult( + protocolVersion=PROTOCOL_VERSION, + capabilities=mock_capabilities, + serverInfo={"name": "MCP Gateway", "version": "1.0.0"}, + instructions="MCP Gateway providing federated tools, resources and prompts.", + ) + mock_handle_initialize.return_value = mock_result + + # Test request + request_data = {"protocol_version": PROTOCOL_VERSION, "capabilities": {}, "client_info": {"name": "Test Client", "version": "1.0.0"}} + + response = test_client.post("/protocol/initialize", json=request_data, headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert result["protocolVersion"] == PROTOCOL_VERSION + assert "capabilities" in result + assert "serverInfo" in result + + # Verify mocks called + mock_handle_initialize.assert_called_once() + + @patch("mcpgateway.main.validate_request") + def test_ping_endpoint(self, mock_validate, test_client, auth_headers): + """Test the ping endpoint.""" + # Test request + request_data = {"jsonrpc": "2.0", "method": "ping", "id": "test-id"} + + response = test_client.post("/protocol/ping", json=request_data, headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert result["jsonrpc"] == "2.0" + assert result["id"] == "test-id" + assert result["result"] == {} + + @patch("mcpgateway.main.server_service.list_servers") + def test_list_servers_endpoint(self, mock_list_servers, test_client, auth_headers): + """Test listing servers endpoint.""" + # Set up mock + mock_servers = [ + ServerRead( + id=1, + name="test_server", + description="A test server", + icon="server-icon", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + associated_tools=[101], + associated_resources=[201], + associated_prompts=[301], + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ] + mock_list_servers.return_value = mock_servers + + # Make request + response = test_client.get("/servers/", headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert len(result) == 1 + assert result[0]["name"] == "test_server" + assert result[0]["id"] == 1 + + # Verify mock called + mock_list_servers.assert_called_once() + + @patch("mcpgateway.main.tool_service.list_tools") + def test_list_tools_endpoint(self, mock_list_tools, test_client, auth_headers): + """Test listing tools endpoint.""" + # Set up mock + mock_tools = [ + ToolRead( + id=1, + name="test_tool", + url="http://example.com/tools/test", + description="A test tool", + integration_type="MCP", + request_type="POST", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"param": {"type": "string"}}}, + jsonpath_filter="", + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + gateway_id=None, + execution_count=0, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ] + mock_list_tools.return_value = mock_tools + + # Make request + response = test_client.get("/tools/", headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert len(result) == 1 + assert result[0]["name"] == "test_tool" + assert result[0]["id"] == 1 + + # Verify mock called + mock_list_tools.assert_called_once() + + @patch("mcpgateway.main.resource_service.list_resources") + def test_list_resources_endpoint(self, mock_list_resources, test_client, auth_headers): + """Test listing resources endpoint.""" + # Set up mock + mock_resources = [ + ResourceRead( + id=1, + uri="test/resource", + name="Test Resource", + description="A test resource", + mime_type="text/plain", + size=12, + created_at="2023-01-01T00:00:00", + updated_at="2023-01-01T00:00:00", + is_active=True, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) + ] + mock_list_resources.return_value = mock_resources + + # Make request + response = test_client.get("/resources/", headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert len(result) == 1 + assert result[0]["name"] == "Test Resource" + assert result[0]["uri"] == "test/resource" + + # Verify mock called + mock_list_resources.assert_called_once() + + @patch("mcpgateway.main.resource_service.read_resource") + def test_read_resource_endpoint(self, mock_read_resource, test_client, auth_headers): + """Test reading a resource endpoint.""" + # Set up mock + mock_content = ResourceContent(type="resource", uri="test/resource", mime_type="text/plain", text="This is test content") + mock_read_resource.return_value = mock_content + + # Make request + response = test_client.get("/resources/test/resource", headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert result["type"] == "resource" + assert result["uri"] == "test/resource" + assert result["text"] == "This is test content" + + # Verify mock called + mock_read_resource.assert_called_once() + + @patch("mcpgateway.main.tool_service.invoke_tool") + def test_rpc_tool_invocation(self, mock_invoke_tool, test_client, auth_headers): + """Test tool invocation via RPC endpoint.""" + # Set up test data + tool_name = "test_tool" + tool_args = {"param": "value"} + tool_result = {"content": [{"type": "text", "text": "Tool response"}], "is_error": False} + + # Set up mock + mock_invoke_tool.return_value = tool_result + + # Create RPC request + rpc_request = {"jsonrpc": "2.0", "id": "test-id", "method": tool_name, "params": tool_args} + + # Make request + response = test_client.post("/rpc/", json=rpc_request, headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert "content" in result + assert len(result["content"]) == 1 + assert result["content"][0]["text"] == "Tool response" + + # Verify mock called + mock_invoke_tool.assert_called_once_with(ANY, tool_name, tool_args) + + @patch("mcpgateway.main.prompt_service.get_prompt") + @patch("mcpgateway.main.validate_request") + def test_rpc_prompt_get(self, mock_validate, mock_get_prompt, test_client, auth_headers): + """Test getting a prompt via RPC endpoint.""" + # Set up test data + prompt_name = "test_prompt" + prompt_args = {"param": "value"} + prompt_result = {"messages": [{"role": "user", "content": {"type": "text", "text": "Rendered prompt"}}], "description": "A test prompt"} + + # Set up mock + mock_get_prompt.return_value = prompt_result + + # Create RPC request + rpc_request = {"jsonrpc": "2.0", "id": "test-id", "method": "prompts/get", "params": {"name": prompt_name, "arguments": prompt_args}} + + # Make request + response = test_client.post("/rpc/", json=rpc_request, headers=auth_headers) + + # Verify response + assert response.status_code == 200 + result = response.json() + assert "messages" in result + assert result["messages"][0]["content"]["text"] == "Rendered prompt" + + # Verify mock called + mock_get_prompt.assert_called_once_with(ANY, prompt_name, prompt_args) + + @patch("mcpgateway.main.validate_request") + def test_invalid_request(self, mock_validate, test_client, auth_headers): + """Test handling of invalid RPC requests.""" + # Set up mock to raise error + mock_validate.side_effect = Exception("Invalid request") + + # Create RPC request + rpc_request = {"jsonrpc": "1.0", "id": "test-id", "method": "invalid_method"} # Invalid version + + # Make request + response = test_client.post("/rpc/", json=rpc_request, headers=auth_headers) + + # Verify response indicates error + assert response.status_code == 200 # RPC returns 200 with error in body + result = response.json() + assert "error" in result + assert result["error"]["message"] == "Internal error" + assert "Invalid request" in result["error"]["data"] diff --git a/tests/unit/mcpgateway/test_schemas.py b/tests/unit/mcpgateway/test_schemas.py new file mode 100644 index 0000000000..ba24dd8c6f --- /dev/null +++ b/tests/unit/mcpgateway/test_schemas.py @@ -0,0 +1,829 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the MCP types module. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module contains tests for the various MCP protocol type definitions +defined in the types.py module. +""" + +import json +import os +from datetime import datetime +from typing import Dict, List + +import pytest +from pydantic import ValidationError + +from mcpgateway.types import ( + ClientCapabilities, + ContentType, + CreateMessageResult, + ImageContent, + Implementation, + InitializeRequest, + InitializeResult, + ListResourceTemplatesResult, + LogLevel, + Message, + ModelHint, + ModelPreferences, + PromptArgument, + PromptReference, + PromptResult, + Resource, + ResourceContent, + ResourceReference, + ResourceTemplate, + Role, + Root, + SamplingMessage, + ServerCapabilities, + TextContent, + Tool, + ToolResult, +) + +PROTOCOL_VERSION = os.getenv("PROTOCOL_VERSION", "2025-03-26") + + +class TestMCPTypes: + """Test suite for MCP protocol types.""" + + def test_role_enum(self): + """Test Role enum values.""" + assert Role.ASSISTANT == "assistant" + assert Role.USER == "user" + + def test_log_level_enum(self): + """Test LogLevel enum values.""" + assert LogLevel.DEBUG == "debug" + assert LogLevel.INFO == "info" + assert LogLevel.NOTICE == "notice" + assert LogLevel.WARNING == "warning" + assert LogLevel.ERROR == "error" + assert LogLevel.CRITICAL == "critical" + assert LogLevel.ALERT == "alert" + assert LogLevel.EMERGENCY == "emergency" + + def test_text_content(self): + """Test TextContent model.""" + content = TextContent(type="text", text="Hello, world!") + assert content.type == "text" + assert content.text == "Hello, world!" + + # Test serialization + json_str = content.model_dump_json() + loaded = json.loads(json_str) + assert loaded["type"] == "text" + assert loaded["text"] == "Hello, world!" + + # Test missing required field + with pytest.raises(ValidationError): + TextContent(type="text") + + def test_image_content(self): + """Test ImageContent model.""" + content = ImageContent( + type="image", + data=b"binary_image_data", + mime_type="image/png", + ) + assert content.type == "image" + assert content.data == b"binary_image_data" + assert content.mime_type == "image/png" + + # Test validation errors + with pytest.raises(ValidationError): + ImageContent(type="image", data=b"data") # Missing mime_type + + def test_resource_content(self): + """Test ResourceContent model.""" + # Text resource + text_resource = ResourceContent( + type="resource", + uri="file:///example.txt", + mime_type="text/plain", + text="Example content", + ) + assert text_resource.type == "resource" + assert text_resource.uri == "file:///example.txt" + assert text_resource.mime_type == "text/plain" + assert text_resource.text == "Example content" + assert text_resource.blob is None + + # Binary resource + binary_resource = ResourceContent( + type="resource", + uri="file:///example.bin", + mime_type="application/octet-stream", + blob=b"binary_data", + ) + assert binary_resource.type == "resource" + assert binary_resource.uri == "file:///example.bin" + assert binary_resource.mime_type == "application/octet-stream" + assert binary_resource.text is None + assert binary_resource.blob == b"binary_data" + + # Minimal required fields + minimal = ResourceContent( + type="resource", + uri="file:///example", + ) + assert minimal.type == "resource" + assert minimal.uri == "file:///example" + assert minimal.mime_type is None + assert minimal.text is None + assert minimal.blob is None + + def test_message(self): + """Test Message model with different content types.""" + text_message = Message( + role=Role.USER, + content=TextContent(type="text", text="Hello, world!"), + ) + assert text_message.role == Role.USER + assert text_message.content.type == "text" + assert text_message.content.text == "Hello, world!" + + image_message = Message( + role=Role.ASSISTANT, + content=ImageContent( + type="image", + data=b"binary_image_data", + mime_type="image/png", + ), + ) + assert image_message.role == Role.ASSISTANT + assert image_message.content.type == "image" + assert image_message.content.data == b"binary_image_data" + + def test_prompt_argument(self): + """Test PromptArgument model.""" + # Full argument + arg = PromptArgument( + name="language", + description="Programming language", + required=True, + ) + assert arg.name == "language" + assert arg.description == "Programming language" + assert arg.required is True + + # Minimal argument + minimal = PromptArgument(name="limit") + assert minimal.name == "limit" + assert minimal.description is None + assert minimal.required is False + + def test_prompt_result(self): + """Test PromptResult model.""" + result = PromptResult( + messages=[ + Message( + role=Role.USER, + content=TextContent(type="text", text="Hello, world!"), + ), + Message( + role=Role.ASSISTANT, + content=TextContent(type="text", text="Hi there!"), + ), + ], + description="Example prompt result", + ) + assert len(result.messages) == 2 + assert result.messages[0].role == Role.USER + assert result.messages[1].role == Role.ASSISTANT + assert result.description == "Example prompt result" + + # Test with minimal fields + minimal = PromptResult( + messages=[ + Message( + role=Role.USER, + content=TextContent(type="text", text="Query"), + ), + ], + ) + assert len(minimal.messages) == 1 + assert minimal.description is None + + def test_tool_result(self): + """Test ToolResult model.""" + result = ToolResult( + content=[ + TextContent(type="text", text="Result data"), + ImageContent( + type="image", + data=b"image_data", + mime_type="image/jpeg", + ), + ], + is_error=False, + ) + assert len(result.content) == 2 + assert result.content[0].type == "text" + assert result.content[1].type == "image" + assert result.is_error is False + + # Test error result + error_result = ToolResult( + content=[TextContent(type="text", text="Error message")], + is_error=True, + ) + assert len(error_result.content) == 1 + assert error_result.is_error is True + + def test_resource(self): + """Test Resource model.""" + resource = Resource( + uri="file:///example.txt", + name="Example Resource", + description="An example resource", + mime_type="text/plain", + size=1024, + ) + assert resource.uri == "file:///example.txt" + assert resource.name == "Example Resource" + assert resource.description == "An example resource" + assert resource.mime_type == "text/plain" + assert resource.size == 1024 + + # Test minimal fields + minimal = Resource( + uri="file:///example.bin", + name="Minimal Example", + ) + assert minimal.uri == "file:///example.bin" + assert minimal.name == "Minimal Example" + assert minimal.description is None + assert minimal.mime_type is None + assert minimal.size is None + + def test_resource_template(self): + """Test ResourceTemplate model.""" + template = ResourceTemplate( + uri_template="file:///data/{user}/{file}", + name="User Data Template", + description="Template for user data files", + mime_type="application/octet-stream", + ) + assert template.uri_template == "file:///data/{user}/{file}" + assert template.name == "User Data Template" + assert template.description == "Template for user data files" + assert template.mime_type == "application/octet-stream" + + # Test minimal fields + minimal = ResourceTemplate( + uri_template="file:///logs/{date}.log", + name="Log Template", + ) + assert minimal.uri_template == "file:///logs/{date}.log" + assert minimal.name == "Log Template" + assert minimal.description is None + assert minimal.mime_type is None + + def test_list_resource_templates_result(self): + """Test ListResourceTemplatesResult model.""" + result = ListResourceTemplatesResult( + _meta={"version": "1.0"}, + next_cursor="abc123", + resource_templates=[ + ResourceTemplate( + uri_template="file:///data/{user}/{file}", + name="User Data Template", + ), + ResourceTemplate( + uri_template="file:///logs/{date}.log", + name="Log Template", + ), + ], + ) + assert result.meta == {"version": "1.0"} + assert result.next_cursor == "abc123" + assert len(result.resource_templates) == 2 + assert result.resource_templates[0].name == "User Data Template" + assert result.resource_templates[1].name == "Log Template" + + # Test minimal fields + minimal = ListResourceTemplatesResult( + resource_templates=[ + ResourceTemplate( + uri_template="file:///data/{file}", + name="Simple Template", + ), + ], + ) + assert minimal.meta is None + assert minimal.next_cursor is None + assert len(minimal.resource_templates) == 1 + + def test_root(self): + """Test Root model.""" + root = Root( + uri="file:///data", + name="Data Directory", + ) + assert root.uri == "file:///data" + assert root.name == "Data Directory" + + # Test minimal fields + minimal = Root(uri="file:///logs") + assert minimal.uri == "file:///logs" + assert minimal.name is None + + def test_implementation(self): + """Test Implementation model.""" + impl = Implementation(name="Test Gateway", version="1.0.0") + assert impl.name == "Test Gateway" + assert impl.version == "1.0.0" + + def test_model_hint(self): + """Test ModelHint model.""" + hint = ModelHint(name="gpt-4") + assert hint.name == "gpt-4" + + # Test empty hint + empty = ModelHint() + assert empty.name is None + + def test_model_preferences(self): + """Test ModelPreferences model.""" + prefs = ModelPreferences( + cost_priority=0.8, + speed_priority=0.5, + intelligence_priority=0.2, + hints=[ModelHint(name="claude-3")], + ) + assert prefs.cost_priority == 0.8 + assert prefs.speed_priority == 0.5 + assert prefs.intelligence_priority == 0.2 + assert len(prefs.hints) == 1 + assert prefs.hints[0].name == "claude-3" + + # Test minimal fields + minimal = ModelPreferences( + cost_priority=0.5, + speed_priority=0.5, + intelligence_priority=0.5, + ) + assert minimal.cost_priority == 0.5 + assert minimal.speed_priority == 0.5 + assert minimal.intelligence_priority == 0.5 + assert len(minimal.hints) == 0 + + # Test validation (priorities must be between 0 and 1) + with pytest.raises(ValidationError): + ModelPreferences( + cost_priority=1.5, # Invalid: > 1 + speed_priority=0.5, + intelligence_priority=0.5, + ) + + def test_client_capabilities(self): + """Test ClientCapabilities model.""" + caps = ClientCapabilities( + roots={"listChanged": True}, + sampling={"supports_temperature": True}, + experimental={"feature": {"enabled": True}}, + ) + assert caps.roots == {"listChanged": True} + assert caps.sampling == {"supports_temperature": True} + assert caps.experimental == {"feature": {"enabled": True}} + + # Test minimal fields + minimal = ClientCapabilities() + assert minimal.roots is None + assert minimal.sampling is None + assert minimal.experimental is None + + def test_server_capabilities(self): + """Test ServerCapabilities model.""" + caps = ServerCapabilities( + prompts={"listChanged": True}, + resources={"subscribe": True, "listChanged": True}, + tools={"listChanged": True}, + logging={"setLevel": True}, + experimental={"feature": {"enabled": True}}, + ) + assert caps.prompts == {"listChanged": True} + assert caps.resources == {"subscribe": True, "listChanged": True} + assert caps.tools == {"listChanged": True} + assert caps.logging == {"setLevel": True} + assert caps.experimental == {"feature": {"enabled": True}} + + # Test minimal fields + minimal = ServerCapabilities() + assert minimal.prompts is None + assert minimal.resources is None + assert minimal.tools is None + assert minimal.logging is None + assert minimal.experimental is None + + def test_initialize_request(self): + """Test InitializeRequest model.""" + request = InitializeRequest( + protocol_version=PROTOCOL_VERSION, + capabilities=ClientCapabilities(roots={"listChanged": True}), + client_info=Implementation(name="Test Client", version="1.0.0"), + ) + assert request.protocol_version == PROTOCOL_VERSION + assert request.capabilities.roots == {"listChanged": True} + assert request.client_info.name == "Test Client" + assert request.client_info.version == "1.0.0" + + # Test with field aliases + dict_data = { + "protocolVersion": PROTOCOL_VERSION, + "capabilities": {"roots": {"listChanged": True}}, + "clientInfo": {"name": "Test Client", "version": "1.0.0"}, + } + from_dict = InitializeRequest.model_validate(dict_data) + assert from_dict.protocol_version == PROTOCOL_VERSION + assert from_dict.capabilities.roots == {"listChanged": True} + assert from_dict.client_info.name == "Test Client" + + def test_initialize_result(self): + """Test InitializeResult model.""" + result = InitializeResult( + protocol_version=PROTOCOL_VERSION, + capabilities=ServerCapabilities( + prompts={"listChanged": True}, + resources={"subscribe": True}, + tools={"listChanged": True}, + ), + server_info=Implementation(name="Test Server", version="1.0.0"), + instructions="Example instructions for the client.", + ) + assert result.protocol_version == PROTOCOL_VERSION + assert result.capabilities.prompts == {"listChanged": True} + assert result.capabilities.resources == {"subscribe": True} + assert result.capabilities.tools == {"listChanged": True} + assert result.server_info.name == "Test Server" + assert result.server_info.version == "1.0.0" + assert result.instructions == "Example instructions for the client." + + # Test with field aliases + dict_data = { + "protocolVersion": PROTOCOL_VERSION, + "capabilities": { + "prompts": {"listChanged": True}, + "resources": {"subscribe": True}, + "tools": {"listChanged": True}, + }, + "serverInfo": {"name": "Test Server", "version": "1.0.0"}, + } + from_dict = InitializeResult.model_validate(dict_data) + assert from_dict.protocol_version == PROTOCOL_VERSION + assert from_dict.capabilities.prompts == {"listChanged": True} + assert from_dict.server_info.name == "Test Server" + assert from_dict.instructions is None + + def test_sampling_message(self): + """Test SamplingMessage model.""" + message = SamplingMessage( + role=Role.USER, + content=TextContent(type="text", text="Sample text"), + ) + assert message.role == Role.USER + assert message.content.type == "text" + assert message.content.text == "Sample text" + + def test_create_message_result(self): + """Test CreateMessageResult model.""" + result = CreateMessageResult( + content=TextContent(type="text", text="Generated response"), + model="claude-3", + role=Role.ASSISTANT, + stop_reason="maxTokens", + ) + assert result.content.type == "text" + assert result.content.text == "Generated response" + assert result.model == "claude-3" + assert result.role == Role.ASSISTANT + assert result.stop_reason == "maxTokens" + + # Test minimal fields + minimal = CreateMessageResult( + content=TextContent(type="text", text="Response"), + model="gpt-4", + role=Role.ASSISTANT, + ) + assert minimal.content.text == "Response" + assert minimal.model == "gpt-4" + assert minimal.role == Role.ASSISTANT + assert minimal.stop_reason is None + + def test_prompt_reference(self): + """Test PromptReference model.""" + ref = PromptReference(type="ref/prompt", name="example-prompt") + assert ref.type == "ref/prompt" + assert ref.name == "example-prompt" + + def test_resource_reference(self): + """Test ResourceReference model.""" + ref = ResourceReference(type="ref/resource", uri="file:///example.txt") + assert ref.type == "ref/resource" + assert ref.uri == "file:///example.txt" + + def test_tool(self): + """Test Tool model.""" + tool = Tool( + name="example-tool", + url="http://localhost:8000/tool", + description="An example tool", + integrationType="MCP", + requestType="SSE", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"query": {"type": "string"}}}, + auth_type="bearer", + auth_token="example-token", + ) + assert tool.name == "example-tool" + assert str(tool.url) == "http://localhost:8000/tool" + assert tool.description == "An example tool" + assert tool.integrationType == "MCP" + assert tool.requestType == "SSE" + assert tool.headers == {"Content-Type": "application/json"} + assert tool.input_schema == {"type": "object", "properties": {"query": {"type": "string"}}} + assert tool.auth_type == "bearer" + assert tool.auth_token == "example-token" + + # Test minimal fields + minimal = Tool( + name="minimal-tool", + url="http://localhost:8000/minimal", + ) + assert minimal.name == "minimal-tool" + assert str(minimal.url) == "http://localhost:8000/minimal" + assert minimal.description is None + assert minimal.integrationType == "MCP" # Default value + assert minimal.requestType == "SSE" # Default value + assert minimal.headers == {} # Default value + assert minimal.input_schema == {"type": "object", "properties": {}} # Default value + assert minimal.auth_type is None + assert minimal.auth_username is None + assert minimal.auth_password is None + assert minimal.auth_token is None + + +class TestEventAndAdminSchemas: + """Test event and admin schemas.""" + + def test_event_message(self): + """Test EventMessage model.""" + now = datetime.utcnow() + + event = EventMessage( + type="resource_updated", + data={"uri": "/test/resource.txt", "content": "Updated content"}, + timestamp=now, + ) + + assert event.type == "resource_updated" + assert event.data == {"uri": "/test/resource.txt", "content": "Updated content"} + assert event.timestamp == now + + # Test with default timestamp + default_event = EventMessage( + type="tool_added", + data={"name": "new-tool", "url": "http://example.com/tool"}, + ) + + assert default_event.type == "tool_added" + assert default_event.data == {"name": "new-tool", "url": "http://example.com/tool"} + assert isinstance(default_event.timestamp, datetime) + + def test_admin_tool_create(self): + """Test AdminToolCreate model.""" + tool = AdminToolCreate( + name="admin-tool", + url="http://example.com/admin-tool", + description="Admin tool", + integration_type="MCP", + headers='{"Content-Type": "application/json"}', + input_schema='{"type": "object", "properties": {"query": {"type": "string"}}}', + ) + + assert tool.name == "admin-tool" + assert tool.url == "http://example.com/admin-tool" + assert tool.description == "Admin tool" + assert tool.integration_type == "MCP" + + # JSON string fields should be parsed into dictionaries + assert isinstance(tool.headers, dict) + assert tool.headers == {"Content-Type": "application/json"} + assert isinstance(tool.input_schema, dict) + assert tool.input_schema["type"] == "object" + + # Test with invalid JSON + with pytest.raises(ValidationError): + AdminToolCreate( + name="invalid-tool", + url="http://example.com/invalid", + headers="invalid json", + ) + + def test_admin_gateway_create(self): + """Test AdminGatewayCreate model.""" + gateway = AdminGatewayCreate( + name="Admin Gateway", + url="http://admin-gateway.example.com", + description="Admin gateway instance", + ) + + assert gateway.name == "Admin Gateway" + assert gateway.url == "http://admin-gateway.example.com" + assert gateway.description == "Admin gateway instance" + + # Minimal gateway + minimal = AdminGatewayCreate( + name="Minimal Admin Gateway", + url="http://minimal-admin.example.com", + ) + + assert minimal.name == "Minimal Admin Gateway" + assert minimal.url == "http://minimal-admin.example.com" + assert minimal.description is None + + +class TestServerSchemas: + """Test server-related schemas.""" + + def test_server_create(self): + """Test ServerCreate model.""" + server = ServerCreate( + name="Test Server", + description="Test server instance", + icon="http://example.com/server.png", + associated_tools=["1", "2", "3"], + associated_resources=["4", "5"], + associated_prompts=["6"], + ) + + assert server.name == "Test Server" + assert server.description == "Test server instance" + assert server.icon == "http://example.com/server.png" + assert server.associated_tools == ["1", "2", "3"] + assert server.associated_resources == ["4", "5"] + assert server.associated_prompts == ["6"] + + # Test with comma-separated strings for associations + csv_server = ServerCreate( + name="CSV Server", + description="Server with comma-separated values", + associated_tools="1,2,3", + associated_resources="4,5", + associated_prompts="6", + ) + + assert csv_server.name == "CSV Server" + assert csv_server.associated_tools == ["1", "2", "3"] + assert csv_server.associated_resources == ["4", "5"] + assert csv_server.associated_prompts == ["6"] + + # Minimal server + minimal = ServerCreate( + name="Minimal Server", + ) + + assert minimal.name == "Minimal Server" + assert minimal.description is None + assert minimal.icon is None + assert minimal.associated_tools is None + assert minimal.associated_resources is None + assert minimal.associated_prompts is None + + def test_server_update(self): + """Test ServerUpdate model.""" + update = ServerUpdate( + name="Updated Server", + description="Updated description", + icon="http://example.com/updated.png", + associated_tools=["10", "11"], + associated_resources=["12", "13"], + associated_prompts=["14"], + ) + + assert update.name == "Updated Server" + assert update.description == "Updated description" + assert update.icon == "http://example.com/updated.png" + assert update.associated_tools == ["10", "11"] + assert update.associated_resources == ["12", "13"] + assert update.associated_prompts == ["14"] + + # Test with comma-separated strings + csv_update = ServerUpdate( + associated_tools="10,11", + associated_resources="12,13", + associated_prompts="14", + ) + + assert csv_update.name is None + assert csv_update.description is None + assert csv_update.icon is None + assert csv_update.associated_tools == ["10", "11"] + assert csv_update.associated_resources == ["12", "13"] + assert csv_update.associated_prompts == ["14"] + + def test_server_read(self): + """Test ServerRead model.""" + now = datetime.utcnow() + one_hour_ago = now - timedelta(hours=1) + + server = ServerRead( + id=1, + name="Test Server", + description="Test server instance", + icon="http://example.com/server.png", + created_at=one_hour_ago, + updated_at=now, + is_active=True, + associated_tools=[1, 2, 3], + associated_resources=[4, 5], + associated_prompts=[6], + metrics=ServerMetrics( + total_executions=100, + successful_executions=95, + failed_executions=5, + failure_rate=0.05, + min_response_time=0.1, + max_response_time=2.0, + avg_response_time=0.5, + last_execution_time=now, + ), + ) + + assert server.id == 1 + assert server.name == "Test Server" + assert server.description == "Test server instance" + assert server.icon == "http://example.com/server.png" + assert server.created_at == one_hour_ago + assert server.updated_at == now + assert server.is_active is True + assert server.associated_tools == [1, 2, 3] + assert server.associated_resources == [4, 5] + assert server.associated_prompts == [6] + assert server.metrics.total_executions == 100 + assert server.metrics.successful_executions == 95 + + # Test root validator for associated IDs + server_with_objects = ServerRead( + id=2, + name="Object Server", + description="Server with object associations", + created_at=one_hour_ago, + updated_at=now, + is_active=True, + associated_tools=[Mock(id=10), Mock(id=11)], + associated_resources=[Mock(id=12)], + associated_prompts=[Mock(id=13)], + metrics=ServerMetrics( + total_executions=10, + successful_executions=10, + failed_executions=0, + failure_rate=0.0, + ), + ) + + assert server_with_objects.associated_tools == [10, 11] + assert server_with_objects.associated_resources == [12] + assert server_with_objects.associated_prompts == [13] + + +class TestToggleAndListSchemas: + """Test status toggle and list filter schemas.""" + + def test_status_toggle_request(self): + """Test StatusToggleRequest model.""" + request = StatusToggleRequest(activate=True) + assert request.activate is True + + request_off = StatusToggleRequest(activate=False) + assert request_off.activate is False + + def test_status_toggle_response(self): + """Test StatusToggleResponse model.""" + response = StatusToggleResponse( + id=1, + name="Test Item", + is_active=True, + message="Item activated successfully", + ) + + assert response.id == 1 + assert response.name == "Test Item" + assert response.is_active is True + assert response.message == "Item activated successfully" + + def test_list_filters(self): + """Test ListFilters model.""" + filters = ListFilters(include_inactive=True) + assert filters.include_inactive is True + + # Test default value + default_filters = ListFilters() + assert default_filters.include_inactive is False diff --git a/tests/unit/mcpgateway/test_types.py b/tests/unit/mcpgateway/test_types.py new file mode 100644 index 0000000000..990f2ebd50 --- /dev/null +++ b/tests/unit/mcpgateway/test_types.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the MCP types module. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module contains tests for the various MCP protocol type definitions +defined in the types.py module. +""" + +import json +import os +from datetime import datetime +from typing import Dict, List + +import pytest +from pydantic import ValidationError + +from mcpgateway.types import ( + ClientCapabilities, + ContentType, + CreateMessageResult, + ImageContent, + Implementation, + InitializeRequest, + InitializeResult, + ListResourceTemplatesResult, + LogLevel, + Message, + ModelHint, + ModelPreferences, + PromptArgument, + PromptReference, + PromptResult, + Resource, + ResourceContent, + ResourceReference, + ResourceTemplate, + Role, + Root, + SamplingMessage, + ServerCapabilities, + TextContent, + Tool, + ToolResult, +) + +PROTOCOL_VERSION = os.getenv("PROTOCOL_VERSION", "2025-03-26") + + +class TestMCPTypes: + """Test suite for MCP protocol types.""" + + def test_role_enum(self): + """Test Role enum values.""" + assert Role.ASSISTANT == "assistant" + assert Role.USER == "user" + + def test_log_level_enum(self): + """Test LogLevel enum values.""" + assert LogLevel.DEBUG == "debug" + assert LogLevel.INFO == "info" + assert LogLevel.NOTICE == "notice" + assert LogLevel.WARNING == "warning" + assert LogLevel.ERROR == "error" + assert LogLevel.CRITICAL == "critical" + assert LogLevel.ALERT == "alert" + assert LogLevel.EMERGENCY == "emergency" + + def test_text_content(self): + """Test TextContent model.""" + content = TextContent(type="text", text="Hello, world!") + assert content.type == "text" + assert content.text == "Hello, world!" + + # Test serialization + json_str = content.model_dump_json() + loaded = json.loads(json_str) + assert loaded["type"] == "text" + assert loaded["text"] == "Hello, world!" + + # Test missing required field + with pytest.raises(ValidationError): + TextContent(type="text") + + def test_image_content(self): + """Test ImageContent model.""" + content = ImageContent( + type="image", + data=b"binary_image_data", + mime_type="image/png", + ) + assert content.type == "image" + assert content.data == b"binary_image_data" + assert content.mime_type == "image/png" + + # Test validation errors + with pytest.raises(ValidationError): + ImageContent(type="image", data=b"data") # Missing mime_type + + def test_resource_content(self): + """Test ResourceContent model.""" + # Text resource + text_resource = ResourceContent( + type="resource", + uri="file:///example.txt", + mime_type="text/plain", + text="Example content", + ) + assert text_resource.type == "resource" + assert text_resource.uri == "file:///example.txt" + assert text_resource.mime_type == "text/plain" + assert text_resource.text == "Example content" + assert text_resource.blob is None + + # Binary resource + binary_resource = ResourceContent( + type="resource", + uri="file:///example.bin", + mime_type="application/octet-stream", + blob=b"binary_data", + ) + assert binary_resource.type == "resource" + assert binary_resource.uri == "file:///example.bin" + assert binary_resource.mime_type == "application/octet-stream" + assert binary_resource.text is None + assert binary_resource.blob == b"binary_data" + + # Minimal required fields + minimal = ResourceContent( + type="resource", + uri="file:///example", + ) + assert minimal.type == "resource" + assert minimal.uri == "file:///example" + assert minimal.mime_type is None + assert minimal.text is None + assert minimal.blob is None + + def test_message(self): + """Test Message model with different content types.""" + text_message = Message( + role=Role.USER, + content=TextContent(type="text", text="Hello, world!"), + ) + assert text_message.role == Role.USER + assert text_message.content.type == "text" + assert text_message.content.text == "Hello, world!" + + image_message = Message( + role=Role.ASSISTANT, + content=ImageContent( + type="image", + data=b"binary_image_data", + mime_type="image/png", + ), + ) + assert image_message.role == Role.ASSISTANT + assert image_message.content.type == "image" + assert image_message.content.data == b"binary_image_data" + + def test_prompt_argument(self): + """Test PromptArgument model.""" + # Full argument + arg = PromptArgument( + name="language", + description="Programming language", + required=True, + ) + assert arg.name == "language" + assert arg.description == "Programming language" + assert arg.required is True + + # Minimal argument + minimal = PromptArgument(name="limit") + assert minimal.name == "limit" + assert minimal.description is None + assert minimal.required is False + + def test_prompt_result(self): + """Test PromptResult model.""" + result = PromptResult( + messages=[ + Message( + role=Role.USER, + content=TextContent(type="text", text="Hello, world!"), + ), + Message( + role=Role.ASSISTANT, + content=TextContent(type="text", text="Hi there!"), + ), + ], + description="Example prompt result", + ) + assert len(result.messages) == 2 + assert result.messages[0].role == Role.USER + assert result.messages[1].role == Role.ASSISTANT + assert result.description == "Example prompt result" + + # Test with minimal fields + minimal = PromptResult( + messages=[ + Message( + role=Role.USER, + content=TextContent(type="text", text="Query"), + ), + ], + ) + assert len(minimal.messages) == 1 + assert minimal.description is None + + def test_tool_result(self): + """Test ToolResult model.""" + result = ToolResult( + content=[ + TextContent(type="text", text="Result data"), + ImageContent( + type="image", + data=b"image_data", + mime_type="image/jpeg", + ), + ], + is_error=False, + ) + assert len(result.content) == 2 + assert result.content[0].type == "text" + assert result.content[1].type == "image" + assert result.is_error is False + + # Test error result + error_result = ToolResult( + content=[TextContent(type="text", text="Error message")], + is_error=True, + ) + assert len(error_result.content) == 1 + assert error_result.is_error is True + + def test_resource(self): + """Test Resource model.""" + resource = Resource( + uri="file:///example.txt", + name="Example Resource", + description="An example resource", + mime_type="text/plain", + size=1024, + ) + assert resource.uri == "file:///example.txt" + assert resource.name == "Example Resource" + assert resource.description == "An example resource" + assert resource.mime_type == "text/plain" + assert resource.size == 1024 + + # Test minimal fields + minimal = Resource( + uri="file:///example.bin", + name="Minimal Example", + ) + assert minimal.uri == "file:///example.bin" + assert minimal.name == "Minimal Example" + assert minimal.description is None + assert minimal.mime_type is None + assert minimal.size is None + + def test_resource_template(self): + """Test ResourceTemplate model.""" + template = ResourceTemplate( + uri_template="file:///data/{user}/{file}", + name="User Data Template", + description="Template for user data files", + mime_type="application/octet-stream", + ) + assert template.uri_template == "file:///data/{user}/{file}" + assert template.name == "User Data Template" + assert template.description == "Template for user data files" + assert template.mime_type == "application/octet-stream" + + # Test minimal fields + minimal = ResourceTemplate( + uri_template="file:///logs/{date}.log", + name="Log Template", + ) + assert minimal.uri_template == "file:///logs/{date}.log" + assert minimal.name == "Log Template" + assert minimal.description is None + assert minimal.mime_type is None + + def test_list_resource_templates_result(self): + """Test ListResourceTemplatesResult model.""" + result = ListResourceTemplatesResult( + _meta={"version": "1.0"}, + next_cursor="abc123", + resource_templates=[ + ResourceTemplate( + uri_template="file:///data/{user}/{file}", + name="User Data Template", + ), + ResourceTemplate( + uri_template="file:///logs/{date}.log", + name="Log Template", + ), + ], + ) + assert result.meta == {"version": "1.0"} + assert result.next_cursor == "abc123" + assert len(result.resource_templates) == 2 + assert result.resource_templates[0].name == "User Data Template" + assert result.resource_templates[1].name == "Log Template" + + # Test minimal fields + minimal = ListResourceTemplatesResult( + resource_templates=[ + ResourceTemplate( + uri_template="file:///data/{file}", + name="Simple Template", + ), + ], + ) + assert minimal.meta is None + assert minimal.next_cursor is None + assert len(minimal.resource_templates) == 1 + + def test_root(self): + """Test Root model.""" + root = Root( + uri="file:///data", + name="Data Directory", + ) + assert root.uri == "file:///data" + assert root.name == "Data Directory" + + # Test minimal fields + minimal = Root(uri="file:///logs") + assert minimal.uri == "file:///logs" + assert minimal.name is None + + def test_implementation(self): + """Test Implementation model.""" + impl = Implementation(name="Test Gateway", version="1.0.0") + assert impl.name == "Test Gateway" + assert impl.version == "1.0.0" + + def test_model_hint(self): + """Test ModelHint model.""" + hint = ModelHint(name="gpt-4") + assert hint.name == "gpt-4" + + # Test empty hint + empty = ModelHint() + assert empty.name is None + + def test_model_preferences(self): + """Test ModelPreferences model.""" + prefs = ModelPreferences( + cost_priority=0.8, + speed_priority=0.5, + intelligence_priority=0.2, + hints=[ModelHint(name="claude-3")], + ) + assert prefs.cost_priority == 0.8 + assert prefs.speed_priority == 0.5 + assert prefs.intelligence_priority == 0.2 + assert len(prefs.hints) == 1 + assert prefs.hints[0].name == "claude-3" + + # Test minimal fields + minimal = ModelPreferences( + cost_priority=0.5, + speed_priority=0.5, + intelligence_priority=0.5, + ) + assert minimal.cost_priority == 0.5 + assert minimal.speed_priority == 0.5 + assert minimal.intelligence_priority == 0.5 + assert len(minimal.hints) == 0 + + # Test validation (priorities must be between 0 and 1) + with pytest.raises(ValidationError): + ModelPreferences( + cost_priority=1.5, # Invalid: > 1 + speed_priority=0.5, + intelligence_priority=0.5, + ) + + def test_client_capabilities(self): + """Test ClientCapabilities model.""" + caps = ClientCapabilities( + roots={"listChanged": True}, + sampling={"supports_temperature": True}, + experimental={"feature": {"enabled": True}}, + ) + assert caps.roots == {"listChanged": True} + assert caps.sampling == {"supports_temperature": True} + assert caps.experimental == {"feature": {"enabled": True}} + + # Test minimal fields + minimal = ClientCapabilities() + assert minimal.roots is None + assert minimal.sampling is None + assert minimal.experimental is None + + def test_server_capabilities(self): + """Test ServerCapabilities model.""" + caps = ServerCapabilities( + prompts={"listChanged": True}, + resources={"subscribe": True, "listChanged": True}, + tools={"listChanged": True}, + logging={"setLevel": True}, + experimental={"feature": {"enabled": True}}, + ) + assert caps.prompts == {"listChanged": True} + assert caps.resources == {"subscribe": True, "listChanged": True} + assert caps.tools == {"listChanged": True} + assert caps.logging == {"setLevel": True} + assert caps.experimental == {"feature": {"enabled": True}} + + # Test minimal fields + minimal = ServerCapabilities() + assert minimal.prompts is None + assert minimal.resources is None + assert minimal.tools is None + assert minimal.logging is None + assert minimal.experimental is None + + def test_initialize_request(self): + """Test InitializeRequest model.""" + request = InitializeRequest( + protocol_version=PROTOCOL_VERSION, + capabilities=ClientCapabilities(roots={"listChanged": True}), + client_info=Implementation(name="Test Client", version="1.0.0"), + ) + assert request.protocol_version == PROTOCOL_VERSION + assert request.capabilities.roots == {"listChanged": True} + assert request.client_info.name == "Test Client" + assert request.client_info.version == "1.0.0" + + # Test with field aliases + dict_data = { + "protocolVersion": PROTOCOL_VERSION, + "capabilities": {"roots": {"listChanged": True}}, + "clientInfo": {"name": "Test Client", "version": "1.0.0"}, + } + from_dict = InitializeRequest.model_validate(dict_data) + assert from_dict.protocol_version == PROTOCOL_VERSION + assert from_dict.capabilities.roots == {"listChanged": True} + assert from_dict.client_info.name == "Test Client" + + def test_initialize_result(self): + """Test InitializeResult model.""" + result = InitializeResult( + protocol_version=PROTOCOL_VERSION, + capabilities=ServerCapabilities( + prompts={"listChanged": True}, + resources={"subscribe": True}, + tools={"listChanged": True}, + ), + server_info=Implementation(name="Test Server", version="1.0.0"), + instructions="Example instructions for the client.", + ) + assert result.protocol_version == PROTOCOL_VERSION + assert result.capabilities.prompts == {"listChanged": True} + assert result.capabilities.resources == {"subscribe": True} + assert result.capabilities.tools == {"listChanged": True} + assert result.server_info.name == "Test Server" + assert result.server_info.version == "1.0.0" + assert result.instructions == "Example instructions for the client." + + # Test with field aliases + dict_data = { + "protocolVersion": PROTOCOL_VERSION, + "capabilities": { + "prompts": {"listChanged": True}, + "resources": {"subscribe": True}, + "tools": {"listChanged": True}, + }, + "serverInfo": {"name": "Test Server", "version": "1.0.0"}, + } + from_dict = InitializeResult.model_validate(dict_data) + assert from_dict.protocol_version == PROTOCOL_VERSION + assert from_dict.capabilities.prompts == {"listChanged": True} + assert from_dict.server_info.name == "Test Server" + assert from_dict.instructions is None + + def test_sampling_message(self): + """Test SamplingMessage model.""" + message = SamplingMessage( + role=Role.USER, + content=TextContent(type="text", text="Sample text"), + ) + assert message.role == Role.USER + assert message.content.type == "text" + assert message.content.text == "Sample text" + + def test_create_message_result(self): + """Test CreateMessageResult model.""" + result = CreateMessageResult( + content=TextContent(type="text", text="Generated response"), + model="claude-3", + role=Role.ASSISTANT, + stop_reason="maxTokens", + ) + assert result.content.type == "text" + assert result.content.text == "Generated response" + assert result.model == "claude-3" + assert result.role == Role.ASSISTANT + assert result.stop_reason == "maxTokens" + + # Test minimal fields + minimal = CreateMessageResult( + content=TextContent(type="text", text="Response"), + model="gpt-4", + role=Role.ASSISTANT, + ) + assert minimal.content.text == "Response" + assert minimal.model == "gpt-4" + assert minimal.role == Role.ASSISTANT + assert minimal.stop_reason is None + + def test_prompt_reference(self): + """Test PromptReference model.""" + ref = PromptReference(type="ref/prompt", name="example-prompt") + assert ref.type == "ref/prompt" + assert ref.name == "example-prompt" + + def test_resource_reference(self): + """Test ResourceReference model.""" + ref = ResourceReference(type="ref/resource", uri="file:///example.txt") + assert ref.type == "ref/resource" + assert ref.uri == "file:///example.txt" + + def test_tool(self): + """Test Tool model.""" + tool = Tool( + name="example-tool", + url="http://localhost:8000/tool", + description="An example tool", + integrationType="MCP", + requestType="SSE", + headers={"Content-Type": "application/json"}, + input_schema={"type": "object", "properties": {"query": {"type": "string"}}}, + auth_type="bearer", + auth_token="example-token", + ) + assert tool.name == "example-tool" + assert str(tool.url) == "http://localhost:8000/tool" + assert tool.description == "An example tool" + assert tool.integrationType == "MCP" + assert tool.requestType == "SSE" + assert tool.headers == {"Content-Type": "application/json"} + assert tool.input_schema == {"type": "object", "properties": {"query": {"type": "string"}}} + assert tool.auth_type == "bearer" + assert tool.auth_token == "example-token" + + # Test minimal fields + minimal = Tool( + name="minimal-tool", + url="http://localhost:8000/minimal", + ) + assert minimal.name == "minimal-tool" + assert str(minimal.url) == "http://localhost:8000/minimal" + assert minimal.description is None + assert minimal.integrationType == "MCP" # Default value + assert minimal.requestType == "SSE" # Default value + assert minimal.headers == {} # Default value + assert minimal.input_schema == {"type": "object", "properties": {}} # Default value + assert minimal.auth_type is None + assert minimal.auth_username is None + assert minimal.auth_password is None + assert minimal.auth_token is None diff --git a/tests/unit/mcpgateway/transports/__init__.py b/tests/unit/mcpgateway/transports/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/transports/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/transports/test_base.py b/tests/unit/mcpgateway/transports/test_base.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/transports/test_base.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/transports/test_sse_transport.py b/tests/unit/mcpgateway/transports/test_sse_transport.py new file mode 100644 index 0000000000..4616efa8fb --- /dev/null +++ b/tests/unit/mcpgateway/transports/test_sse_transport.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for the MCP Gateway SSE transport implementation. +""" + +import asyncio +import json +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest +from fastapi import Request +from sse_starlette.sse import EventSourceResponse + +from mcpgateway.transports.sse_transport import SSETransport + + +@pytest.fixture +def sse_transport(): + """Create an SSE transport instance.""" + return SSETransport(base_url="http://test.example") + + +@pytest.fixture +def mock_request(): + """Create a mock FastAPI request.""" + mock = Mock(spec=Request) + return mock + + +class TestSSETransport: + """Tests for the SSETransport class.""" + + @pytest.mark.asyncio + async def test_connect_disconnect(self, sse_transport): + """Test connecting and disconnecting from SSE transport.""" + # Initially should not be connected + assert await sse_transport.is_connected() is False + + # Connect + await sse_transport.connect() + assert await sse_transport.is_connected() is True + assert sse_transport._connected is True + + # Disconnect + await sse_transport.disconnect() + assert await sse_transport.is_connected() is False + assert sse_transport._connected is False + assert sse_transport._client_gone.is_set() + + @pytest.mark.asyncio + async def test_send_message(self, sse_transport): + """Test sending a message over SSE.""" + # Connect first + await sse_transport.connect() + + # Test message + message = {"jsonrpc": "2.0", "method": "test", "id": 1} + + # Send message + await sse_transport.send_message(message) + + # Verify message was queued + assert sse_transport._message_queue.qsize() == 1 + queued_message = await sse_transport._message_queue.get() + assert queued_message == message + + @pytest.mark.asyncio + async def test_send_message_not_connected(self, sse_transport): + """Test sending message when not connected raises error.""" + # Don't connect + message = {"jsonrpc": "2.0", "method": "test", "id": 1} + + # Should raise error + with pytest.raises(RuntimeError, match="Transport not connected"): + await sse_transport.send_message(message) + + @pytest.mark.asyncio + async def test_create_sse_response(self, sse_transport, mock_request): + """Test creating SSE response.""" + # Connect first + await sse_transport.connect() + + # Create SSE response + response = await sse_transport.create_sse_response(mock_request) + + # Should be an EventSourceResponse + assert isinstance(response, EventSourceResponse) + + # Verify response headers + assert response.status_code == 200 + assert response.headers["Cache-Control"] == "no-cache" + assert response.headers["Content-Type"] == "text/event-stream" + assert response.headers["X-MCP-SSE"] == "true" + + @pytest.mark.asyncio + async def test_receive_message(self, sse_transport): + """Test receiving messages from client.""" + # Connect first + await sse_transport.connect() + + # Get receive generator + receive_gen = sse_transport.receive_message() + + # Should yield initialize message first + first_message = await receive_gen.__anext__() + assert first_message["jsonrpc"] == "2.0" + assert first_message["method"] == "initialize" + + # Trigger client disconnection to end the loop + sse_transport._client_gone.set() + + # Wait for the generator to end + with pytest.raises(StopAsyncIteration): + # Use a timeout in case the generator doesn't end + async def wait_with_timeout(): + await asyncio.wait_for(receive_gen.__anext__(), timeout=1.0) + + await wait_with_timeout() + + @pytest.mark.asyncio + async def test_event_generator(self, sse_transport, mock_request): + """Test the event generator for SSE.""" + # Connect first + await sse_transport.connect() + + # Create SSE response + response = await sse_transport.create_sse_response(mock_request) + + # Access the generator from the response + generator = response.body_iterator + + # First event should be endpoint + event = await generator.__anext__() + assert "event" in event + assert event["event"] == "endpoint" + assert sse_transport._session_id in event["data"] + + # Second event should be keepalive + event = await generator.__anext__() + assert event["event"] == "keepalive" + + # Queue a test message + test_message = {"jsonrpc": "2.0", "result": "test", "id": 1} + await sse_transport._message_queue.put(test_message) + + # Next event should be the message + event = await generator.__anext__() + assert event["event"] == "message" + assert json.loads(event["data"]) == test_message + + # Cancel the generator to clean up + sse_transport._client_gone.set() diff --git a/tests/unit/mcpgateway/transports/test_stdio_transport.py b/tests/unit/mcpgateway/transports/test_stdio_transport.py new file mode 100644 index 0000000000..5f37f52fe7 --- /dev/null +++ b/tests/unit/mcpgateway/transports/test_stdio_transport.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the stdio transport implementation. + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +This module tests the stdio transport for MCP, ensuring it properly handles +communication over standard input/output streams. +""" + +import asyncio +import json +import sys +from io import StringIO +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import pytest + +from mcpgateway.transports.stdio_transport import StdioTransport + + +@pytest.fixture +def stdio_transport(): + """Create a StdioTransport instance for testing.""" + return StdioTransport() + + +class TestStdioTransport: + """Test suite for the StdioTransport class.""" + + @patch("asyncio.get_running_loop") + async def test_connect(self, mock_get_loop, stdio_transport): + """Test establishing a connection.""" + # Set up mocks + mock_loop = MagicMock() + mock_get_loop.return_value = mock_loop + + mock_reader = MagicMock() + mock_reader_protocol = MagicMock() + + mock_transport = MagicMock() + mock_protocol = MagicMock() + + # Mock the connect_read_pipe and connect_write_pipe methods + mock_loop.connect_read_pipe = AsyncMock(return_value=(mock_transport, mock_reader_protocol)) + mock_loop.connect_write_pipe = AsyncMock(return_value=(mock_transport, mock_protocol)) + + # Call the method under test + await stdio_transport.connect() + + # Verify the expected calls + mock_get_loop.assert_called_once() + mock_loop.connect_read_pipe.assert_called_once() + mock_loop.connect_write_pipe.assert_called_once() + + # Verify the connection state + assert stdio_transport._connected is True + + @patch("asyncio.StreamWriter") + async def test_disconnect(self, mock_writer, stdio_transport): + """Test closing a connection.""" + # Set up mock + stdio_transport._stdout_writer = mock_writer + stdio_transport._connected = True + + # Call the method under test + await stdio_transport.disconnect() + + # Verify the expected calls + mock_writer.close.assert_called_once() + mock_writer.wait_closed.assert_called_once() + + # Verify the connection state + assert stdio_transport._connected is False + + async def test_disconnect_not_connected(self, stdio_transport): + """Test disconnecting when not connected.""" + # Ensure not connected + stdio_transport._stdout_writer = None + stdio_transport._connected = False + + # Call the method under test + await stdio_transport.disconnect() + + # Verify the connection state + assert stdio_transport._connected is False + + async def test_send_message_not_connected(self, stdio_transport): + """Test sending a message when not connected.""" + # Ensure not connected + stdio_transport._stdout_writer = None + stdio_transport._connected = False + + # Verify the expected exception + with pytest.raises(RuntimeError, match="Transport not connected"): + await stdio_transport.send_message({"type": "test"}) + + @patch("asyncio.StreamWriter") + async def test_send_message(self, mock_writer, stdio_transport): + """Test sending a message.""" + # Set up mock + stdio_transport._stdout_writer = mock_writer + stdio_transport._connected = True + + # Call the method under test + await stdio_transport.send_message({"type": "test", "data": "message"}) + + # Verify the expected calls + mock_writer.write.assert_called_once() + mock_writer.drain.assert_called_once() + + # Verify the encoded message + call_args = mock_writer.write.call_args[0][0] + assert b'{"type": "test", "data": "message"}\n' == call_args + + @patch("asyncio.StreamWriter") + async def test_send_message_exception(self, mock_writer, stdio_transport): + """Test sending a message when an error occurs.""" + # Set up mock with an exception + stdio_transport._stdout_writer = mock_writer + stdio_transport._connected = True + mock_writer.write.side_effect = Exception("Write error") + + # Verify the expected exception + with pytest.raises(Exception, match="Write error"): + await stdio_transport.send_message({"type": "test"}) + + async def test_receive_message_not_connected(self, stdio_transport): + """Test receiving messages when not connected.""" + # Ensure not connected + stdio_transport._stdin_reader = None + stdio_transport._connected = False + + # Verify the expected exception + with pytest.raises(RuntimeError, match="Transport not connected"): + async for _ in stdio_transport.receive_message(): + pass + + @patch("asyncio.StreamReader") + async def test_receive_message(self, mock_reader, stdio_transport): + """Test receiving messages.""" + # Set up mock with two messages followed by EOF + stdio_transport._stdin_reader = mock_reader + stdio_transport._connected = True + + message1 = b'{"type": "message1", "data": "test1"}\n' + message2 = b'{"type": "message2", "data": "test2"}\n' + + # Configure the mock to return messages and then an empty response (EOF) + mock_reader.readline = AsyncMock(side_effect=[message1, message2, b""]) + + # Collect received messages + received = [] + async for message in stdio_transport.receive_message(): + received.append(message) + + # Verify the expected calls and received messages + assert mock_reader.readline.call_count == 3 + assert len(received) == 2 + assert received[0] == {"type": "message1", "data": "test1"} + assert received[1] == {"type": "message2", "data": "test2"} + + @patch("asyncio.StreamReader") + async def test_receive_message_exception(self, mock_reader, stdio_transport): + """Test receiving messages when a non-fatal error occurs.""" + # Set up mock with a valid message, then an invalid one, then EOF + stdio_transport._stdin_reader = mock_reader + stdio_transport._connected = True + + message1 = b'{"type": "message1", "data": "test1"}\n' + invalid_message = b"not valid json\n" + + # Configure the mock to return a valid message, then an invalid one, then EOF + mock_reader.readline = AsyncMock(side_effect=[message1, invalid_message, b""]) + + # Collect received messages (should only get the valid one) + received = [] + async for message in stdio_transport.receive_message(): + received.append(message) + + # Verify the expected calls and received messages + assert mock_reader.readline.call_count == 3 + assert len(received) == 1 + assert received[0] == {"type": "message1", "data": "test1"} + + @patch("asyncio.StreamReader") + async def test_receive_message_cancellation(self, mock_reader, stdio_transport): + """Test receiving messages with cancellation.""" + # Set up mock with a message + stdio_transport._stdin_reader = mock_reader + stdio_transport._connected = True + + message = b'{"type": "message", "data": "test"}\n' + + # Configure the mock to return a message and then raise a cancellation + mock_reader.readline = AsyncMock(side_effect=[message, asyncio.CancelledError()]) + + # Collect received messages until cancellation + received = [] + try: + async for message in stdio_transport.receive_message(): + received.append(message) + except asyncio.CancelledError: + pass # Expected + + # Verify the expected calls and received messages + assert mock_reader.readline.call_count == 2 + assert len(received) == 1 + assert received[0] == {"type": "message", "data": "test"} + + async def test_is_connected(self, stdio_transport): + """Test checking connection status.""" + # Test when not connected + stdio_transport._connected = False + assert await stdio_transport.is_connected() is False + + # Test when connected + stdio_transport._connected = True + assert await stdio_transport.is_connected() is True + + @patch.object(asyncio, "StreamReader") + @patch.object(asyncio, "get_running_loop") + async def test_full_lifecycle(self, mock_get_loop, mock_stream_reader, stdio_transport): + """Test a full lifecycle of connect, send/receive, and disconnect.""" + # Set up mocks + mock_loop = MagicMock() + mock_get_loop.return_value = mock_loop + + mock_reader = MagicMock() + mock_reader_protocol = MagicMock() + + mock_transport = MagicMock() + mock_protocol = MagicMock() + mock_writer = MagicMock() + + # Mock the connect_read_pipe and connect_write_pipe methods + mock_loop.connect_read_pipe = AsyncMock(return_value=(mock_reader_protocol, mock_protocol)) + mock_loop.connect_write_pipe = AsyncMock(return_value=(mock_transport, mock_protocol)) + + # Add stdout writer + stdio_transport._stdout_writer = mock_writer + + # Configure reader with a test message + mock_message = b'{"type": "test", "content": "hello"}\n' + mock_reader.readline = AsyncMock(return_value=mock_message) + stdio_transport._stdin_reader = mock_reader + + # Test connect + await stdio_transport.connect() + assert stdio_transport._connected is True + + # Test is_connected + assert await stdio_transport.is_connected() is True + + # Test send_message + await stdio_transport.send_message({"type": "response", "content": "world"}) + mock_writer.write.assert_called_once() + mock_writer.drain.assert_called_once() + + # Test receive_message + async def get_first_message(): + async for message in stdio_transport.receive_message(): + return message + + message = await get_first_message() + assert message == {"type": "test", "content": "hello"} + + # Test disconnect + await stdio_transport.disconnect() + assert stdio_transport._connected is False + mock_writer.close.assert_called_once() + mock_writer.wait_closed.assert_called_once() diff --git a/tests/unit/mcpgateway/transports/test_websocket_transport.py b/tests/unit/mcpgateway/transports/test_websocket_transport.py new file mode 100644 index 0000000000..5687932387 --- /dev/null +++ b/tests/unit/mcpgateway/transports/test_websocket_transport.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +Tests for the MCP Gateway WebSocket transport implementation. +""" + +import asyncio +import json +from unittest.mock import AsyncMock, Mock, patch + +import pytest +from fastapi import WebSocket, WebSocketDisconnect + +from mcpgateway.transports.websocket_transport import WebSocketTransport + + +@pytest.fixture +def mock_websocket(): + """Create a mock WebSocket.""" + mock = AsyncMock(spec=WebSocket) + mock.accept = AsyncMock() + mock.send_json = AsyncMock() + mock.send_bytes = AsyncMock() + mock.receive_json = AsyncMock() + mock.receive_bytes = AsyncMock() + mock.close = AsyncMock() + return mock + + +@pytest.fixture +def websocket_transport(mock_websocket): + """Create a WebSocket transport with a mock WebSocket.""" + return WebSocketTransport(websocket=mock_websocket) + + +class TestWebSocketTransport: + """Tests for the WebSocketTransport class.""" + + @pytest.mark.asyncio + async def test_connect(self, websocket_transport, mock_websocket): + """Test connecting to WebSocket transport.""" + # Initially should not be connected + assert await websocket_transport.is_connected() is False + + # Connect + await websocket_transport.connect() + + # Should have accepted the connection + mock_websocket.accept.assert_called_once() + assert await websocket_transport.is_connected() is True + + @pytest.mark.asyncio + async def test_disconnect(self, websocket_transport, mock_websocket): + """Test disconnecting from WebSocket transport.""" + # Connect first + await websocket_transport.connect() + assert await websocket_transport.is_connected() is True + + # Disconnect + await websocket_transport.disconnect() + + # Should have closed the connection + mock_websocket.close.assert_called_once() + assert await websocket_transport.is_connected() is False + + @pytest.mark.asyncio + async def test_send_message(self, websocket_transport, mock_websocket): + """Test sending a message over WebSocket.""" + # Connect first + await websocket_transport.connect() + + # Send message + test_message = {"jsonrpc": "2.0", "method": "test", "id": 1} + await websocket_transport.send_message(test_message) + + # Should have sent the message + mock_websocket.send_json.assert_called_once_with(test_message) + + @pytest.mark.asyncio + async def test_send_message_not_connected(self, websocket_transport): + """Test sending message when not connected raises error.""" + # Don't connect + test_message = {"jsonrpc": "2.0", "method": "test", "id": 1} + + # Should raise error + with pytest.raises(RuntimeError, match="Transport not connected"): + await websocket_transport.send_message(test_message) + + @pytest.mark.asyncio + async def test_receive_message(self, websocket_transport, mock_websocket): + """Test receiving messages from WebSocket.""" + # Connect first + await websocket_transport.connect() + + # Set up return values + test_messages = [ + {"jsonrpc": "2.0", "method": "test1", "id": 1}, + {"jsonrpc": "2.0", "method": "test2", "id": 2}, + WebSocketDisconnect(), # Raise this after the second message + ] + + mock_websocket.receive_json.side_effect = [ + test_messages[0], + test_messages[1], + WebSocketDisconnect(), + ] + + # Get message generator + receive_gen = websocket_transport.receive_message() + + # First message + message1 = await receive_gen.__anext__() + assert message1 == test_messages[0] + + # Second message + message2 = await receive_gen.__anext__() + assert message2 == test_messages[1] + + # Third should raise StopAsyncIteration due to WebSocketDisconnect + with pytest.raises(StopAsyncIteration): + await receive_gen.__anext__() + + # Connection should be closed + assert await websocket_transport.is_connected() is False + + @pytest.mark.asyncio + async def test_send_ping(self, websocket_transport, mock_websocket): + """Test sending a ping message.""" + # Connect first + await websocket_transport.connect() + + # Send ping + await websocket_transport.send_ping() + + # Should have sent ping bytes + mock_websocket.send_bytes.assert_called_once_with(b"ping") + + @pytest.mark.asyncio + async def test_ping_loop(self, websocket_transport, mock_websocket): + """Test the ping loop with valid response.""" + # Mock dependencies + with patch("mcpgateway.config.settings") as mock_settings, patch("asyncio.sleep", new_callable=AsyncMock): + + # Configure settings + mock_settings.websocket_ping_interval = 0.1 # Short interval for testing + + # Connect + await websocket_transport.connect() + + # Mock responses to check loop behavior + mock_websocket.receive_bytes.return_value = b"pong" + + # Start ping task + ping_task = asyncio.create_task(websocket_transport._ping_loop()) + + # Let it run a little + await asyncio.sleep(0.2) + + # Should have sent at least one ping + assert mock_websocket.send_bytes.call_count >= 1 + mock_websocket.send_bytes.assert_called_with(b"ping") + + # Should have received pong + assert mock_websocket.receive_bytes.call_count >= 1 + + # Cancel task to clean up + ping_task.cancel() + try: + await ping_task + except asyncio.CancelledError: + pass diff --git a/tests/unit/mcpgateway/types/__init__.py b/tests/unit/mcpgateway/types/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/types/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/types/test_types.py b/tests/unit/mcpgateway/types/test_types.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/types/test_types.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/validation/__init__.py b/tests/unit/mcpgateway/validation/__init__.py new file mode 100644 index 0000000000..3e7f7eefad --- /dev/null +++ b/tests/unit/mcpgateway/validation/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" diff --git a/tests/unit/mcpgateway/validation/test_jsonrpc.py b/tests/unit/mcpgateway/validation/test_jsonrpc.py new file mode 100644 index 0000000000..2fdb370cc6 --- /dev/null +++ b/tests/unit/mcpgateway/validation/test_jsonrpc.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +""" + +Copyright 2025 +SPDX-License-Identifier: Apache-2.0 +Authors: Mihai Criveti + +""" +import pytest + +from mcpgateway.validation.jsonrpc import ( + INVALID_REQUEST, + JSONRPCError, + validate_request, + validate_response, +) + + +class TestJSONRPCValidation: + """Tests for JSON-RPC validation functions.""" + + def test_validate_valid_request(self): + """Test validation of valid JSON-RPC requests.""" + # Minimal valid request + valid_request = {"jsonrpc": "2.0", "method": "test_method", "id": 1} + validate_request(valid_request) # Should not raise + + # Request with params as object + valid_request_with_params = { + "jsonrpc": "2.0", + "method": "test_method", + "params": {"test": "value"}, + "id": 1, + } + validate_request(valid_request_with_params) # Should not raise + + # Request with params as array + valid_request_with_array_params = { + "jsonrpc": "2.0", + "method": "test_method", + "params": [1, 2, 3], + "id": "abc", + } + validate_request(valid_request_with_array_params) # Should not raise + + # Valid notification (without id) + valid_notification = {"jsonrpc": "2.0", "method": "test_method"} + validate_request(valid_notification) # Should not raise + + def test_validate_invalid_request_version(self): + """Test validation fails with invalid JSON-RPC version.""" + # Missing jsonrpc version + invalid_request = {"method": "test_method", "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid JSON-RPC version" in exc.value.message + + # Wrong jsonrpc version + invalid_request = {"jsonrpc": "1.0", "method": "test_method", "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid JSON-RPC version" in exc.value.message + + def test_validate_invalid_request_method(self): + """Test validation fails with invalid method.""" + # Missing method + invalid_request = {"jsonrpc": "2.0", "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid or missing method" in exc.value.message + + # Empty method + invalid_request = {"jsonrpc": "2.0", "method": "", "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid or missing method" in exc.value.message + + # Non-string method + invalid_request = {"jsonrpc": "2.0", "method": 123, "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid or missing method" in exc.value.message + + def test_validate_invalid_request_id(self): + """Test validation fails with invalid request ID.""" + # Boolean ID (not allowed) + invalid_request = {"jsonrpc": "2.0", "method": "test", "id": True} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid request ID type" in exc.value.message + + # Object ID (not allowed) + invalid_request = {"jsonrpc": "2.0", "method": "test", "id": {}} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid request ID type" in exc.value.message + + def test_validate_invalid_request_params(self): + """Test validation fails with invalid params.""" + # Params as boolean (not allowed) + invalid_request = {"jsonrpc": "2.0", "method": "test", "params": True, "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid params type" in exc.value.message + + # Params as string (not allowed) + invalid_request = {"jsonrpc": "2.0", "method": "test", "params": "string", "id": 1} + with pytest.raises(JSONRPCError) as exc: + validate_request(invalid_request) + assert exc.value.code == INVALID_REQUEST + assert "Invalid params type" in exc.value.message + + def test_validate_valid_response(self): + """Test validation of valid JSON-RPC responses.""" + # Valid success response + valid_response = {"jsonrpc": "2.0", "result": "success", "id": 1} + validate_response(valid_response) # Should not raise + + # Valid error response + valid_error_response = { + "jsonrpc": "2.0", + "error": {"code": -32600, "message": "Invalid Request"}, + "id": "abc", + } + validate_response(valid_error_response) # Should not raise + + # Valid response with null ID + valid_null_id_response = {"jsonrpc": "2.0", "result": "success", "id": None} + validate_response(valid_null_id_response) # Should not raise + + def test_jsonrpc_error_to_dict(self): + """Test conversion of JSONRPCError to dict.""" + # Basic error + error = JSONRPCError(code=-32600, message="Test Error", id="test-id") + error_dict = error.to_dict() + assert error_dict["jsonrpc"] == "2.0" + assert error_dict["error"]["code"] == -32600 + assert error_dict["error"]["message"] == "Test Error" + assert error_dict["id"] == "test-id" + assert "data" not in error_dict["error"] + + # Error with data + error = JSONRPCError(code=-32600, message="Test Error", data={"detail": "info"}, id=1) + error_dict = error.to_dict() + assert error_dict["error"]["data"] == {"detail": "info"} diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000000..66f2f552f3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,2974 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "argparse-manpage" +version = "4.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/b7/cacb63bd461c83050b3b0efffc9151077b69de900ef5559ffeee72fa825b/argparse-manpage-4.6.tar.gz", hash = "sha256:0b659d70fd142876da41c2918bd6de4d027875720b0e4672d6443b51198dbb62", size = 58674, upload-time = "2024-06-05T11:46:30.578Z" } + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, +] + +[[package]] +name = "astroid" +version = "3.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/c2/9b2de9ed027f9fe5734a6c0c0a601289d796b3caaf1e372e23fa88a73047/astroid-3.3.10.tar.gz", hash = "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce", size = 398941, upload-time = "2025-05-10T13:33:10.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/58/5260205b9968c20b6457ed82f48f9e3d6edf2f1f95103161798b73aeccf0/astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb", size = 275388, upload-time = "2025-05-10T13:33:08.391Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "autoflake" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyflakes" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/cb/486f912d6171bc5748c311a2984a301f4e2d054833a1da78485866c71522/autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e", size = 27642, upload-time = "2024-03-13T03:41:28.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483, upload-time = "2024-03-13T03:41:26.969Z" }, +] + +[[package]] +name = "bandit" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "stevedore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/a5/144a45f8e67df9d66c3bc3f7e69a39537db8bff1189ab7cff4e9459215da/bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a", size = 4232005, upload-time = "2025-02-17T05:24:57.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/85/db74b9233e0aa27ec96891045c5e920a64dd5cbccd50f8e64e9460f48d35/bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8", size = 129078, upload-time = "2025-02-17T05:24:54.068Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, +] + +[[package]] +name = "binaryornot" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/fe/7ebfec74d49f97fc55cd38240c7a7d08134002b1e14be8c3897c0dd5e49b/binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", size = 371054, upload-time = "2017-08-03T15:55:25.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/7e/f7b6f453e6481d1e233540262ccbfcf89adcd43606f44a028d7f5fae5eb2/binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4", size = 9006, upload-time = "2017-08-03T15:55:31.23Z" }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, + { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, +] + +[[package]] +name = "bracex" +version = "2.5.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/6c/57418c4404cd22fe6275b8301ca2b46a8cdaa8157938017a9ae0b3edf363/bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6", size = 26641, upload-time = "2024-09-28T21:41:22.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/02/8db98cdc1a58e0abd6716d5e63244658e6e63513c65f469f34b6f1053fd0/bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6", size = 11558, upload-time = "2024-09-28T21:41:21.016Z" }, +] + +[[package]] +name = "build" +version = "1.2.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, +] + +[[package]] +name = "bump2version" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/2a/688aca6eeebfe8941235be53f4da780c6edee05dbbea5d7abaa3aab6fad2/bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6", size = 36236, upload-time = "2020-10-07T18:38:40.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/e3/fa60c47d7c344533142eb3af0b73234ef8ea3fb2da742ab976b947e717df/bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", size = 22030, upload-time = "2020-10-07T18:38:38.148Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, +] + +[[package]] +name = "code2flow" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/a5/d1a0d78f1d31d592131eed19c48839c17a842094a683242ec0d0a825830e/code2flow-2.5.1.tar.gz", hash = "sha256:ad98717920da6598dac38a1951f38b84891f4aa4bd2f36c119b0dbac729f4785", size = 313232, upload-time = "2023-01-08T22:56:44.649Z" } + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cookiecutter" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, + { name = "binaryornot" }, + { name = "click" }, + { name = "jinja2" }, + { name = "python-slugify" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/17/9f2cd228eb949a91915acd38d3eecdc9d8893dde353b603f0db7e9f6be55/cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c", size = 158767, upload-time = "2024-02-21T18:02:41.949Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/d9/0137658a353168ffa9d0fc14b812d3834772040858ddd1cb6eeaf09f7a44/cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d", size = 39177, upload-time = "2024-02-21T18:02:39.569Z" }, +] + +[[package]] +name = "coverage" +version = "7.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/01/1c5e6ee4ebaaa5e079db933a9a45f61172048c7efa06648445821a201084/coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe", size = 211379, upload-time = "2025-03-30T20:34:53.904Z" }, + { url = "https://files.pythonhosted.org/packages/e9/16/a463389f5ff916963471f7c13585e5f38c6814607306b3cb4d6b4cf13384/coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28", size = 211814, upload-time = "2025-03-30T20:34:56.959Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b1/77062b0393f54d79064dfb72d2da402657d7c569cfbc724d56ac0f9c67ed/coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3", size = 240937, upload-time = "2025-03-30T20:34:58.751Z" }, + { url = "https://files.pythonhosted.org/packages/d7/54/c7b00a23150083c124e908c352db03bcd33375494a4beb0c6d79b35448b9/coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676", size = 238849, upload-time = "2025-03-30T20:35:00.521Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/a6b7cfebd34e7b49f844788fda94713035372b5200c23088e3bbafb30970/coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d", size = 239986, upload-time = "2025-03-30T20:35:02.307Z" }, + { url = "https://files.pythonhosted.org/packages/21/8c/c965ecef8af54e6d9b11bfbba85d4f6a319399f5f724798498387f3209eb/coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a", size = 239896, upload-time = "2025-03-30T20:35:04.141Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/070550273fb4c480efa8381735969cb403fa8fd1626d74865bfaf9e4d903/coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c", size = 238613, upload-time = "2025-03-30T20:35:05.889Z" }, + { url = "https://files.pythonhosted.org/packages/07/76/fbb2540495b01d996d38e9f8897b861afed356be01160ab4e25471f4fed1/coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f", size = 238909, upload-time = "2025-03-30T20:35:07.76Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7e/76d604db640b7d4a86e5dd730b73e96e12a8185f22b5d0799025121f4dcb/coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f", size = 213948, upload-time = "2025-03-30T20:35:09.144Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a7/f8ce4aafb4a12ab475b56c76a71a40f427740cf496c14e943ade72e25023/coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23", size = 214844, upload-time = "2025-03-30T20:35:10.734Z" }, + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "coverage-badge" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/8f/e92b0a010c76b0da82709838b3f3ae9aec638d0c44dbfb1186a5751f5d2e/coverage_badge-1.1.2.tar.gz", hash = "sha256:fe7ed58a3b72dad85a553b64a99e963dea3847dcd0b8ddd2b38a00333618642c", size = 6335, upload-time = "2024-08-02T23:34:08.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/3d/5642a1a06191b2e1e0f87a2e824e6d3eb7c32c589a68ed4d1dcbd3324d63/coverage_badge-1.1.2-py2.py3-none-any.whl", hash = "sha256:d8413ce51c91043a1692b943616b450868cbeeb0ea6a0c54a32f8318c9c96ff7", size = 6493, upload-time = "2024-08-02T23:34:07.063Z" }, +] + +[[package]] +name = "cryptography" +version = "44.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload-time = "2025-05-02T19:34:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, + { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, + { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, + { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, + { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, + { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload-time = "2025-05-02T19:35:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload-time = "2025-05-02T19:35:12.12Z" }, + { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload-time = "2025-05-02T19:35:13.775Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, + { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, + { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload-time = "2025-05-02T19:35:33.805Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload-time = "2025-05-02T19:35:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/7f/10/abcf7418536df1eaba70e2cfc5c8a0ab07aa7aa02a5cbc6a78b9d8b4f121/cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d", size = 3393192, upload-time = "2025-05-02T19:35:37.468Z" }, + { url = "https://files.pythonhosted.org/packages/06/59/ecb3ef380f5891978f92a7f9120e2852b1df6f0a849c277b8ea45b865db2/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8", size = 3898419, upload-time = "2025-05-02T19:35:39.065Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d0/35e2313dbb38cf793aa242182ad5bc5ef5c8fd4e5dbdc380b936c7d51169/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4", size = 4117892, upload-time = "2025-05-02T19:35:40.839Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c8/31fb6e33b56c2c2100d76de3fd820afaa9d4d0b6aea1ccaf9aaf35dc7ce3/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff", size = 3900855, upload-time = "2025-05-02T19:35:42.599Z" }, + { url = "https://files.pythonhosted.org/packages/43/2a/08cc2ec19e77f2a3cfa2337b429676406d4bb78ddd130a05c458e7b91d73/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06", size = 4117619, upload-time = "2025-05-02T19:35:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/fc3d3f84022a75f2ac4b1a1c0e5d6a0c2ea259e14cd4aae3e0e68e56483c/cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9", size = 3136570, upload-time = "2025-05-02T19:35:46.94Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4b/c11ad0b6c061902de5223892d680e89c06c7c4d606305eb8de56c5427ae6/cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375", size = 3390230, upload-time = "2025-05-02T19:35:49.062Z" }, + { url = "https://files.pythonhosted.org/packages/58/11/0a6bf45d53b9b2290ea3cec30e78b78e6ca29dc101e2e296872a0ffe1335/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647", size = 3895216, upload-time = "2025-05-02T19:35:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/0a/27/b28cdeb7270e957f0077a2c2bfad1b38f72f1f6d699679f97b816ca33642/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259", size = 4115044, upload-time = "2025-05-02T19:35:53.044Z" }, + { url = "https://files.pythonhosted.org/packages/35/b0/ec4082d3793f03cb248881fecefc26015813199b88f33e3e990a43f79835/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff", size = 3898034, upload-time = "2025-05-02T19:35:54.72Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7f/adf62e0b8e8d04d50c9a91282a57628c00c54d4ae75e2b02a223bd1f2613/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5", size = 4114449, upload-time = "2025-05-02T19:35:57.139Z" }, + { url = "https://files.pythonhosted.org/packages/87/62/d69eb4a8ee231f4bf733a92caf9da13f1c81a44e874b1d4080c25ecbb723/cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c", size = 3134369, upload-time = "2025-05-02T19:35:58.907Z" }, +] + +[[package]] +name = "darglint" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/2c/86e8549e349388c18ca8a4ff8661bb5347da550f598656d32a98eaaf91cc/darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da", size = 74435, upload-time = "2021-10-18T03:40:37.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/28/85d1e0396d64422c5218d68e5cdcc53153aa8a2c83c7dbc3ee1502adf3a1/darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d", size = 120767, upload-time = "2021-10-18T03:40:35.034Z" }, +] + +[[package]] +name = "dataclasses-json" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "marshmallow-enum" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/94/1b30216f84c48b9e0646833f6f2dd75f1169cc04dc45c48fe39e644c89d5/dataclasses-json-0.5.7.tar.gz", hash = "sha256:c2c11bc8214fbf709ffc369d11446ff6945254a7f09128154a7620613d8fda90", size = 30958, upload-time = "2022-03-21T14:49:46.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/7e/2042610dfc8121e8119ad8b94db496d8697e4b0ef7a6e378018a2bd84435/dataclasses_json-0.5.7-py3-none-any.whl", hash = "sha256:bc285b5f892094c3a53d558858a88553dd6a61a11ab1a8128a0e554385dcc5dd", size = 25647, upload-time = "2022-03-21T14:49:40.344Z" }, +] + +[[package]] +name = "dataproperty" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mbstrdecoder" }, + { name = "typepy", extra = ["datetime"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/81/8c8b64ae873cb9014815214c07b63b12e3b18835780fb342223cfe3fe7d8/dataproperty-1.1.0.tar.gz", hash = "sha256:b038437a4097d1a1c497695c3586ea34bea67fdd35372b9a50f30bf044d77d04", size = 42574, upload-time = "2024-12-31T14:37:26.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/c2/e12e95e289e6081a40454199ab213139ef16a528c7c86432de545b05a23a/DataProperty-1.1.0-py3-none-any.whl", hash = "sha256:c61fcb2e2deca35e6d1eb1f251a7f22f0dcde63e80e61f0cc18c19f42abfd25b", size = 27581, upload-time = "2024-12-31T14:37:22.657Z" }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, +] + +[[package]] +name = "fastapi" +version = "0.115.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, +] + +[[package]] +name = "fawltydeps" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "isort" }, + { name = "packaging" }, + { name = "pip-requirements-parser" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/36114743a9ee46b7c3eb31f00ec13746cfb5f33da38b6ab08e84c0eb3357/fawltydeps-0.19.0.tar.gz", hash = "sha256:0fbe9b1736f3a1c7060d5b3489e24f594187a0c98afa5ea888b27e70e63b0ae0", size = 48320, upload-time = "2025-02-28T12:50:55.355Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c8/581e08c6d7ead9e4fa9e7ad858609d726687764a83a517f7dac2d904a114/fawltydeps-0.19.0-py3-none-any.whl", hash = "sha256:fbdd3da5620dd3c6b12a8c00ac655d17376aacf6d105fa61fe44fa4fa195baed", size = 55610, upload-time = "2025-02-28T12:50:53.437Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "flake8" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, +] + +[[package]] +name = "gprof2dot" +version = "2025.4.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/fd/cad13fa1f7a463a607176432c4affa33ea162f02f58cc36de1d40d3e6b48/gprof2dot-2025.4.14.tar.gz", hash = "sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce", size = 39536, upload-time = "2025-04-14T07:21:45.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/ed/89d760cb25279109b89eb52975a7b5479700d3114a2421ce735bfb2e7513/gprof2dot-2025.4.14-py3-none-any.whl", hash = "sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e", size = 37555, upload-time = "2025-04-14T07:21:43.319Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/c1/a82edae11d46c0d83481aacaa1e578fea21d94a1ef400afd734d47ad95ad/greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485", size = 185797, upload-time = "2025-05-09T19:47:35.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/66/910217271189cc3f32f670040235f4bf026ded8ca07270667d69c06e7324/greenlet-3.2.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c49e9f7c6f625507ed83a7485366b46cbe325717c60837f7244fc99ba16ba9d6", size = 267395, upload-time = "2025-05-09T14:50:45.357Z" }, + { url = "https://files.pythonhosted.org/packages/a8/36/8d812402ca21017c82880f399309afadb78a0aa300a9b45d741e4df5d954/greenlet-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3cc1a3ed00ecfea8932477f729a9f616ad7347a5e55d50929efa50a86cb7be7", size = 625742, upload-time = "2025-05-09T15:23:58.293Z" }, + { url = "https://files.pythonhosted.org/packages/7b/77/66d7b59dfb7cc1102b2f880bc61cb165ee8998c9ec13c96606ba37e54c77/greenlet-3.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9896249fbef2c615853b890ee854f22c671560226c9221cfd27c995db97e5c", size = 637014, upload-time = "2025-05-09T15:24:47.025Z" }, + { url = "https://files.pythonhosted.org/packages/36/a7/ff0d408f8086a0d9a5aac47fa1b33a040a9fca89bd5a3f7b54d1cd6e2793/greenlet-3.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7409796591d879425997a518138889d8d17e63ada7c99edc0d7a1c22007d4907", size = 632874, upload-time = "2025-05-09T15:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/a1/75/1dc2603bf8184da9ebe69200849c53c3c1dca5b3a3d44d9f5ca06a930550/greenlet-3.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7791dcb496ec53d60c7f1c78eaa156c21f402dda38542a00afc3e20cae0f480f", size = 631652, upload-time = "2025-05-09T14:53:30.961Z" }, + { url = "https://files.pythonhosted.org/packages/7b/74/ddc8c3bd4c2c20548e5bf2b1d2e312a717d44e2eca3eadcfc207b5f5ad80/greenlet-3.2.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8009ae46259e31bc73dc183e402f548e980c96f33a6ef58cc2e7865db012e13", size = 580619, upload-time = "2025-05-09T14:53:42.049Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f2/40f26d7b3077b1c7ae7318a4de1f8ffc1d8ccbad8f1d8979bf5080250fd6/greenlet-3.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fd9fb7c941280e2c837b603850efc93c999ae58aae2b40765ed682a6907ebbc5", size = 1109809, upload-time = "2025-05-09T15:26:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/c5/21/9329e8c276746b0d2318b696606753f5e7b72d478adcf4ad9a975521ea5f/greenlet-3.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:00cd814b8959b95a546e47e8d589610534cfb71f19802ea8a2ad99d95d702057", size = 1133455, upload-time = "2025-05-09T14:53:55.823Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1e/0dca9619dbd736d6981f12f946a497ec21a0ea27262f563bca5729662d4d/greenlet-3.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:d0cb7d47199001de7658c213419358aa8937df767936506db0db7ce1a71f4a2f", size = 294991, upload-time = "2025-05-09T15:05:56.847Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9f/a47e19261747b562ce88219e5ed8c859d42c6e01e73da6fbfa3f08a7be13/greenlet-3.2.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:dcb9cebbf3f62cb1e5afacae90761ccce0effb3adaa32339a0670fe7805d8068", size = 268635, upload-time = "2025-05-09T14:50:39.007Z" }, + { url = "https://files.pythonhosted.org/packages/11/80/a0042b91b66975f82a914d515e81c1944a3023f2ce1ed7a9b22e10b46919/greenlet-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf3fc9145141250907730886b031681dfcc0de1c158f3cc51c092223c0f381ce", size = 628786, upload-time = "2025-05-09T15:24:00.692Z" }, + { url = "https://files.pythonhosted.org/packages/38/a2/8336bf1e691013f72a6ebab55da04db81a11f68e82bb691f434909fa1327/greenlet-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efcdfb9df109e8a3b475c016f60438fcd4be68cd13a365d42b35914cdab4bb2b", size = 640866, upload-time = "2025-05-09T15:24:48.153Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/f2a3a13e424670a5d08826dab7468fa5e403e0fbe0b5f951ff1bc4425b45/greenlet-3.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd139e4943547ce3a56ef4b8b1b9479f9e40bb47e72cc906f0f66b9d0d5cab3", size = 636752, upload-time = "2025-05-09T15:29:23.182Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5d/ce4a03a36d956dcc29b761283f084eb4a3863401c7cb505f113f73af8774/greenlet-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71566302219b17ca354eb274dfd29b8da3c268e41b646f330e324e3967546a74", size = 636028, upload-time = "2025-05-09T14:53:32.854Z" }, + { url = "https://files.pythonhosted.org/packages/4b/29/b130946b57e3ceb039238413790dd3793c5e7b8e14a54968de1fe449a7cf/greenlet-3.2.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3091bc45e6b0c73f225374fefa1536cd91b1e987377b12ef5b19129b07d93ebe", size = 583869, upload-time = "2025-05-09T14:53:43.614Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/9f538dfe7f87b90ecc75e589d20cbd71635531a617a336c386d775725a8b/greenlet-3.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:44671c29da26539a5f142257eaba5110f71887c24d40df3ac87f1117df589e0e", size = 1112886, upload-time = "2025-05-09T15:27:01.304Z" }, + { url = "https://files.pythonhosted.org/packages/be/92/4b7deeb1a1e9c32c1b59fdca1cac3175731c23311ddca2ea28a8b6ada91c/greenlet-3.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c23ea227847c9dbe0b3910f5c0dd95658b607137614eb821e6cbaecd60d81cc6", size = 1138355, upload-time = "2025-05-09T14:53:58.011Z" }, + { url = "https://files.pythonhosted.org/packages/c5/eb/7551c751a2ea6498907b2fcbe31d7a54b602ba5e8eb9550a9695ca25d25c/greenlet-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:0a16fb934fcabfdfacf21d79e6fed81809d8cd97bc1be9d9c89f0e4567143d7b", size = 295437, upload-time = "2025-05-09T15:00:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a1/88fdc6ce0df6ad361a30ed78d24c86ea32acb2b563f33e39e927b1da9ea0/greenlet-3.2.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:df4d1509efd4977e6a844ac96d8be0b9e5aa5d5c77aa27ca9f4d3f92d3fcf330", size = 270413, upload-time = "2025-05-09T14:51:32.455Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/6c1caffd65490c68cd9bcec8cb7feb8ac7b27d38ba1fea121fdc1f2331dc/greenlet-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da956d534a6d1b9841f95ad0f18ace637668f680b1339ca4dcfb2c1837880a0b", size = 637242, upload-time = "2025-05-09T15:24:02.63Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/088af2cedf8823b6b7ab029a5626302af4ca1037cf8b998bed3a8d3cb9e2/greenlet-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c7b15fb9b88d9ee07e076f5a683027bc3befd5bb5d25954bb633c385d8b737e", size = 651444, upload-time = "2025-05-09T15:24:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0116ab876bb0bc7a81eadc21c3f02cd6100dcd25a1cf2a085a130a63a26a/greenlet-3.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752f0e79785e11180ebd2e726c8a88109ded3e2301d40abced2543aa5d164275", size = 646067, upload-time = "2025-05-09T15:29:24.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/17/bb8f9c9580e28a94a9575da847c257953d5eb6e39ca888239183320c1c28/greenlet-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae572c996ae4b5e122331e12bbb971ea49c08cc7c232d1bd43150800a2d6c65", size = 648153, upload-time = "2025-05-09T14:53:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ee/7f31b6f7021b8df6f7203b53b9cc741b939a2591dcc6d899d8042fcf66f2/greenlet-3.2.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02f5972ff02c9cf615357c17ab713737cccfd0eaf69b951084a9fd43f39833d3", size = 603865, upload-time = "2025-05-09T14:53:45.738Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2d/759fa59323b521c6f223276a4fc3d3719475dc9ae4c44c2fe7fc750f8de0/greenlet-3.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4fefc7aa68b34b9224490dfda2e70ccf2131368493add64b4ef2d372955c207e", size = 1119575, upload-time = "2025-05-09T15:27:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/30/05/356813470060bce0e81c3df63ab8cd1967c1ff6f5189760c1a4734d405ba/greenlet-3.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a31ead8411a027c2c4759113cf2bd473690517494f3d6e4bf67064589afcd3c5", size = 1147460, upload-time = "2025-05-09T14:54:00.315Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/b2a26a309a04fb844c7406a4501331b9400e1dd7dd64d3450472fd47d2e1/greenlet-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b24c7844c0a0afc3ccbeb0b807adeefb7eff2b5599229ecedddcfeb0ef333bec", size = 296239, upload-time = "2025-05-09T14:57:17.633Z" }, + { url = "https://files.pythonhosted.org/packages/89/30/97b49779fff8601af20972a62cc4af0c497c1504dfbb3e93be218e093f21/greenlet-3.2.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59", size = 269150, upload-time = "2025-05-09T14:50:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/21/30/877245def4220f684bc2e01df1c2e782c164e84b32e07373992f14a2d107/greenlet-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf", size = 637381, upload-time = "2025-05-09T15:24:12.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/16/adf937908e1f913856b5371c1d8bdaef5f58f251d714085abeea73ecc471/greenlet-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325", size = 651427, upload-time = "2025-05-09T15:24:51.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/49/6d79f58fa695b618654adac64e56aff2eeb13344dc28259af8f505662bb1/greenlet-3.2.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5", size = 645795, upload-time = "2025-05-09T15:29:26.673Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e6/28ed5cb929c6b2f001e96b1d0698c622976cd8f1e41fe7ebc047fa7c6dd4/greenlet-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825", size = 648398, upload-time = "2025-05-09T14:53:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/b200194e25ae86bc57077f695b6cc47ee3118becf54130c5514456cf8dac/greenlet-3.2.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d", size = 606795, upload-time = "2025-05-09T14:53:47.039Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c8/ba1def67513a941154ed8f9477ae6e5a03f645be6b507d3930f72ed508d3/greenlet-3.2.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf", size = 1117976, upload-time = "2025-05-09T15:27:06.542Z" }, + { url = "https://files.pythonhosted.org/packages/c3/30/d0e88c1cfcc1b3331d63c2b54a0a3a4a950ef202fb8b92e772ca714a9221/greenlet-3.2.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708", size = 1145509, upload-time = "2025-05-09T14:54:02.223Z" }, + { url = "https://files.pythonhosted.org/packages/90/2e/59d6491834b6e289051b252cf4776d16da51c7c6ca6a87ff97e3a50aa0cd/greenlet-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421", size = 296023, upload-time = "2025-05-09T14:53:24.157Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/8a73aace5a5335a1cba56d0da71b7bd93e450f17d372c5b7c5fa547557e9/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418", size = 629911, upload-time = "2025-05-09T15:24:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/48/08/c8b8ebac4e0c95dcc68ec99198842e7db53eda4ab3fb0a4e785690883991/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4", size = 635251, upload-time = "2025-05-09T15:24:52.205Z" }, + { url = "https://files.pythonhosted.org/packages/37/26/7db30868f73e86b9125264d2959acabea132b444b88185ba5c462cb8e571/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763", size = 632620, upload-time = "2025-05-09T15:29:28.051Z" }, + { url = "https://files.pythonhosted.org/packages/10/ec/718a3bd56249e729016b0b69bee4adea0dfccf6ca43d147ef3b21edbca16/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b", size = 628851, upload-time = "2025-05-09T14:53:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1c79286a76bc62ccdc1387291464af16a4204ea717f24e77b0acd623b99/greenlet-3.2.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207", size = 593718, upload-time = "2025-05-09T14:53:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/cd/41/96ba2bf948f67b245784cd294b84e3d17933597dffd3acdb367a210d1949/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8", size = 1105752, upload-time = "2025-05-09T15:27:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/68/3b/3b97f9d33c1f2eb081759da62bd6162159db260f602f048bc2f36b4c453e/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51", size = 1125170, upload-time = "2025-05-09T14:54:04.082Z" }, + { url = "https://files.pythonhosted.org/packages/31/df/b7d17d66c8d0f578d2885a3d8f565e9e4725eacc9d3fdc946d0031c055c4/greenlet-3.2.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240", size = 269899, upload-time = "2025-05-09T14:54:01.581Z" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "handsdown" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "black" }, + { name = "importlib-resources" }, + { name = "jinja2" }, + { name = "pip" }, + { name = "typed-ast" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/6e/629b2afdbfd741417c524cff16726df19afb6453b1ddb8f1f1be90fc7a61/handsdown-2.1.0.tar.gz", hash = "sha256:8d54934e96fef949f950b6c5d043b2d9c30bbc64e052906b076c0f376f414786", size = 50165, upload-time = "2024-01-02T19:32:25.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/59/4303741674b44833814bd24d28502b351bef6de8c7688b2a7d564d4a1f84/handsdown-2.1.0-py3-none-any.whl", hash = "sha256:a6497d0513448e9b34d19eb0998654141f68693939e75e6dcfc0b68066fffc29", size = 72124, upload-time = "2024-01-02T19:32:23.087Z" }, +] + +[[package]] +name = "html5lib" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "identify" +version = "2.6.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/83/b6ea0334e2e7327084a46aaaf71f2146fc061a192d6518c0d020120cd0aa/identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8", size = 99201, upload-time = "2025-04-19T15:10:38.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101, upload-time = "2025-04-19T15:10:36.701Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "immutabledict" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/c5/4240186fbabc58fba41bbe17c5f0cd37ffd4c0b85a5029ab104f946df175/immutabledict-4.2.1.tar.gz", hash = "sha256:d91017248981c72eb66c8ff9834e99c2f53562346f23e7f51e7a5ebcf66a3bcc", size = 6228, upload-time = "2024-11-17T13:25:21.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/56/25ca7b848164b7d93dbd5fc97dd7751700c93e324fe854afbeb562ee2f98/immutabledict-4.2.1-py3-none-any.whl", hash = "sha256:c56a26ced38c236f79e74af3ccce53772827cef5c3bce7cab33ff2060f756373", size = 4700, upload-time = "2024-11-17T13:25:19.52Z" }, +] + +[[package]] +name = "importchecker" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/04/cd8c2179797ddbcec15ca90d02d46a54316bb57fb914f931e114ca19111b/importchecker-3.0.tar.gz", hash = "sha256:ce4fd5bd697d4b36bf2816a6c3675e66b620abed550f60b89edf9e2b99055c57", size = 11098, upload-time = "2024-02-16T14:59:39.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/6b/f4f8df231745d1d28183ee9f00ff115426b07a1aaeac6e0977f7f5efe084/importchecker-3.0-py3-none-any.whl", hash = "sha256:8eba2f85ffef681a65c678f6877c41e1f836cfcbd7df0a4132dcef0cf3b7a6f3", size = 10191, upload-time = "2024-02-16T14:59:38.091Z" }, +] + +[[package]] +name = "importlab" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "networkx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/ab9494dccf1e237276f98364d53673bc0ab97ebe5cb671e960f18710457d/importlab-0.8.1.tar.gz", hash = "sha256:b3893853b1f6eb027da509c3b40e6787e95dd66b4b66f1b3613aad77556e1465", size = 28856, upload-time = "2023-10-06T22:43:40.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/1e/cc7360b4259f283b1a2de153335ce15ac9e710d66145aa471cffefe4b394/importlab-0.8.1-py2.py3-none-any.whl", hash = "sha256:124cfa00e8a34fefe8aac1a5e94f56c781b178c9eb61a1d3f60f7e03b77338d3", size = 21671, upload-time = "2023-10-06T22:43:38.997Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "isort" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jq" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/32/3eaca3ac81c804d6849da2e9f536ac200f4ad46a696890854c1f73b2f749/jq-1.8.0.tar.gz", hash = "sha256:53141eebca4bf8b4f2da5e44271a8a3694220dfd22d2b4b2cfb4816b2b6c9057", size = 2058265, upload-time = "2024-08-17T08:13:36.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/ec/f72f8b0272b2d92c99cb33af70833e51af1bf673db39214948aa85699b48/jq-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:628848f92a0f24f5ca50c879d271555a63bf28746c1efd3571ee49e9a357b602", size = 416542, upload-time = "2024-08-17T08:13:42.048Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3c/3b781ae9f4f0dd24e75c0005d3a886b0ae55a684562206a4fd33fdc318c3/jq-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d375b0f372df24087fd0688ef85fef43a44a3e382a82afcc0cdfdfe59e59d313", size = 422189, upload-time = "2024-08-17T08:13:45.096Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b8/2ea11152a3546803bfad5a8ef78b6f4cbfbfe75a7455c6f662728167c09f/jq-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd0c30af5257ae0dccd27c5140726e24108a472e56dce8767b918905adfd9c99", size = 719303, upload-time = "2024-08-17T08:13:48.431Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/3d252898f6163143b8def254b53e626b3f8cfb12c3dddcfacb796a7e396b/jq-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59bda8b62453967a32f418562309d0ffe0da73227e8c5800334ee0b515c5d2e2", size = 737355, upload-time = "2024-08-17T08:13:51.926Z" }, + { url = "https://files.pythonhosted.org/packages/74/6c/85c477f133ee96de376070ee12991a81e7f83300d607203724633dd5ae69/jq-1.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05e2c0a8944a3ff93de6353d60ed69fa85b155c08d6776ab20d4429197f50050", size = 727894, upload-time = "2024-08-17T08:13:55.178Z" }, + { url = "https://files.pythonhosted.org/packages/07/c2/f0d8b7c9669ff17a57e54da469515e6d2badc6ed2b038792162b449aa168/jq-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2526368e5658eaeb47984b551e7178a0216cc8c5fdd6dd343964574cae513c89", size = 697960, upload-time = "2024-08-17T08:13:57.405Z" }, + { url = "https://files.pythonhosted.org/packages/26/16/28b277d52125cbb2681063c875a178a1d11d8f0b7884f5f54b0418219587/jq-1.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:881be44d8f804a97a1e37dc6360bf2deab43768d7fbb31cfb22ca8050dd6aed3", size = 722986, upload-time = "2024-08-17T08:14:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/fe/eb/62b9f6e3bbc4f2a05b392b1d1a4603fc927746d9e33f5c8d24edcfd7d429/jq-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f057322a572fe2cf0cb9ea068dd4eec237bc15490e0944cd979aeb23b20db3ac", size = 725489, upload-time = "2024-08-17T08:14:02.488Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2e/d0eedabd00b0c30a98be50d894825adcc0d302514bad098b4bdcbc0e28f1/jq-1.8.0-cp310-cp310-win32.whl", hash = "sha256:aaf6e17cd9bf26c076a9a6ff0b4bfac66fdaa37ed9e215683de58d657cc75f29", size = 407142, upload-time = "2024-08-17T08:14:04.463Z" }, + { url = "https://files.pythonhosted.org/packages/15/d5/dd01e938759b48df628eb5eb3818bd404c54d2d41e93bfb3ee079dbf16e4/jq-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:53c87ef5491e484cdfb740303ccfc141af1d23275750569f539d4981524f4251", size = 417758, upload-time = "2024-08-17T08:14:06.776Z" }, + { url = "https://files.pythonhosted.org/packages/da/95/dcbef114d8b71d52def6f5ea7a04f892f18803d52e0aaf3d4e6393dcb7d4/jq-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8441fe181af789a05b742930d095ee61fc251fdd2b975c68e359ac7e85a4c2d", size = 416862, upload-time = "2024-08-17T08:14:08.724Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c9/06f04189aa5265827228a31ab531712c5b6345c177988d7e1397b0cb18f7/jq-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e687ef4b360e7436c3b5f15ee25f2570bcbcadccb940ebbc80ebe4b05b91ee2", size = 422413, upload-time = "2024-08-17T08:14:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/77/6a55ae6d41f6298245dc45271a10b319c91eb3176a5fe0b6edd74e4031fb/jq-1.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf862d1bc1d0095aef0efc76f8cef0da7ab996f2b9d34c5067e48427a069ea3", size = 731532, upload-time = "2024-08-17T08:14:15.172Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fe/b7786c4cbf8ff4fd0a9b5273a30ee65a91c6f1bf38414e989a117ccd5c71/jq-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:190fd2bf92b7abec3090a1f68db40cd001178e84c42754f75253ee1f9c17dfdf", size = 746597, upload-time = "2024-08-17T08:14:19.047Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/a2ce5bed9984eb98953184f8b4fea99798996631166f06e60cd5a9db8c51/jq-1.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ecba9f181e7810a336a520f32df998e6ecc9fdebac80c6a636e402baa939e79", size = 739586, upload-time = "2024-08-17T08:14:22.224Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/4b0cff04095fb40ba279beb10746a445fa55755784a2546017e6975e1280/jq-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8b6322f647f9e1d7be7f6e8203106f4ff1b7c0e07c9023607c7414e1dc098b67", size = 722756, upload-time = "2024-08-17T08:14:25.044Z" }, + { url = "https://files.pythonhosted.org/packages/63/63/e93d730108fc0651fbe47ed7f3a52ba134292523ae5f162cfb30e3020b74/jq-1.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7bed3b9cc53d72383fc558cfe03345735e7532d1733a5ed3c2196f1eec1c26d7", size = 746574, upload-time = "2024-08-17T08:14:27.416Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/bc890164f63371dcf90ac1d3383d0f11eefc8ec1ff649407cbd3393f530d/jq-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a01261e4df11d3a0fe42fece73bb458d2e4a33b481d67e5e817acec8b0e923d", size = 749311, upload-time = "2024-08-17T08:14:29.444Z" }, + { url = "https://files.pythonhosted.org/packages/1b/40/31585bd330b4da8895cff6d963c685b3dd444a7d199de367347f89e3825a/jq-1.8.0-cp311-cp311-win32.whl", hash = "sha256:52cac82de5608f9174d22a1a805d61ba47ea182b10a934135904648c618ebe34", size = 405664, upload-time = "2024-08-17T08:14:32.519Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/492d74bbd0fb4aa1ed2539cf4b460f8bb1ff56073cf591fa91dbb399f488/jq-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:745d0f9786bd89eb9bff054ac08ce0e61877d28931857585e244e8674ac3727e", size = 416898, upload-time = "2024-08-17T08:14:35.615Z" }, + { url = "https://files.pythonhosted.org/packages/45/b3/dd0d41cecb0d8712bc792b3c40b42a36c355d814d61f6bda4d61cbb188e5/jq-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14f5988ae3604ebfdba2da398f9bd941bb3a72144a2831cfec2bc22bd23d5563", size = 415943, upload-time = "2024-08-17T08:14:38.437Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2c/39df803632c7222e9cd6922101966ddbec05d1c4213e7923c95e4e442666/jq-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f8903b66fac9f46de72b3a2f69bfa3c638a7a8d52610d1894df87ef0a9e4d2d3", size = 422267, upload-time = "2024-08-17T08:14:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b3/ddc1e691b832c6aa0f5142935099c1f05a89ff2f337201e2dcfafc726ec9/jq-1.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccda466f5722fa9be789099ce253bfc177e49f9a981cb7f5b6369ea37041104", size = 729142, upload-time = "2024-08-17T08:14:44.144Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b9/42a55d08397d25b4b1f6580f58c59ba3e3e120270db2e75923644ccc0d29/jq-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f57649e84a09b334eeb80d22ecc96ff7b31701f3f818ef14cb8bb162c84863", size = 748871, upload-time = "2024-08-17T08:14:46.816Z" }, + { url = "https://files.pythonhosted.org/packages/90/4f/83639fdae641b7e8095b4a51d87a3da46737e70570d9df14d99ea15a0b16/jq-1.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7453731008eb7671725222781eb7bc5ed96e80fc9a652d177cb982276d3e08b4", size = 735908, upload-time = "2024-08-17T08:14:48.865Z" }, + { url = "https://files.pythonhosted.org/packages/f7/9f/f54c2050b21490201613a7328534d2cb0c34e5a547167849a1464d89ae3e/jq-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:917812663613fc0542117bbe7ec43c8733b0c6bb174db6be06a15fc612de3b70", size = 721970, upload-time = "2024-08-17T08:14:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/24/b0/6c9a14ef103df4208e032bce25e66293201dacac18689d2ec4c0e68c8b77/jq-1.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ec9e4db978237470e9d65f747eb459f4ffee576c9c9f8ca92ab32d5687a46e4a", size = 746825, upload-time = "2024-08-17T08:14:53.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/67/4eb836a9eac5f02983ed7caf76c4d0cad32fdd6ae08176be892b3a6b3d17/jq-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9f2548c83473bbe88a32a0735cb949a5d01804f8d411efae5342b5d23be8a2f", size = 751186, upload-time = "2024-08-17T08:14:57.32Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/66739f56ee1e3d144e7eef6453c5967275f75bf216e1915cdd9652a779aa/jq-1.8.0-cp312-cp312-win32.whl", hash = "sha256:e3da3538549d5bdc84e6282555be4ba5a50c3792db7d8d72d064cc6f48a2f722", size = 405483, upload-time = "2024-08-17T08:15:00.532Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9f/e886c23b466fc41f105b715724c19dd6089585f2e34375f07c38c69ceaf1/jq-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:049ba2978e61e593299edc6dd57b9cefd680272740ad1d4703f8784f5fab644d", size = 417281, upload-time = "2024-08-17T08:15:03.048Z" }, + { url = "https://files.pythonhosted.org/packages/9c/25/c73afa16aedee3ae87b2e8ffb2d12bdb9c7a34a8c9ab5038318cb0b431fe/jq-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aea6161c4d975230e85735c0214c386e66035e96cfc4fd69159e87f46c09d4", size = 415000, upload-time = "2024-08-17T08:15:05.25Z" }, + { url = "https://files.pythonhosted.org/packages/06/97/d09338697ea0eb7386a3df0c6ca2a77ab090c19420a85acdc6f36971c6b8/jq-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c24a5f9e3807e277e19f305c8bcd0665b8b89251b053903f611969657680722", size = 421253, upload-time = "2024-08-17T08:15:07.633Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c3/d020c19eca167b5085e74d2277bc3d9e35d1b4ee5bcb9076f1e26882514d/jq-1.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb484525dd801583ebd695d02f9165445a4d1b2fb560b187e6fc654911f0600e", size = 725885, upload-time = "2024-08-17T08:15:10.647Z" }, + { url = "https://files.pythonhosted.org/packages/78/b8/8f6b886856f52f3277663d2d7a199663c6ede589dd0714aac9491b82ba6e/jq-1.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddd9abdf0c1b30be1bf853d8c52187c96a51b2cbc05f40c43a37bf6a9b956807", size = 746334, upload-time = "2024-08-17T08:15:13.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/2fa34e480068863ab372ec91c59b10214e9f8f3ae8b6e2de61456e93bae1/jq-1.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c7464d9b88c74a7119b53f4bbf88028d07a9de9a1a279e45209b763b89d6582", size = 733716, upload-time = "2024-08-17T08:15:15.836Z" }, + { url = "https://files.pythonhosted.org/packages/2e/db/59cb84ec59247af7f7bedd2b5c88b3a4ca17253fd2cc0d40f08573f7ff72/jq-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b99761e8ec2cedb9906df4ceae33f467a377621019ef40a9a275689ac3577456", size = 720978, upload-time = "2024-08-17T08:15:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6f/d04bdcc037ced716e2522ebf7a677541b8654d7855cd1404d894f1ecd144/jq-1.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1be1638f9d5f38c83440fb9626d8f78905ed5d70e926e3a664d3de1198e1ef79", size = 746431, upload-time = "2024-08-17T08:15:19.948Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/f100fb2ccd467c17a2ecc186334aa7b512e49ca1a678ecc53dd4defd6e22/jq-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d7e82d58bf3afe373afb3a01f866e473bbd34f38377a2f216c6222ec028eeea", size = 750404, upload-time = "2024-08-17T08:15:22.198Z" }, + { url = "https://files.pythonhosted.org/packages/86/b4/e2459542207238d86727cf81af321ee4920497757092facf347726d64965/jq-1.8.0-cp313-cp313-win32.whl", hash = "sha256:96cb0bb35d55b19b910b12aba3d72e333ad6348a703494c7738cc4664e4410f0", size = 405691, upload-time = "2024-08-17T08:15:25.346Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4d/6e1230f96052d578439eee4ea28069728f3ad4027de127a93b8c6da142f0/jq-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:53e60a87657efc365a5d9ccfea2b536cddc1ffab190e823f8645ad933b272d51", size = 417930, upload-time = "2024-08-17T08:15:28.487Z" }, + { url = "https://files.pythonhosted.org/packages/10/3a/d8350a87cf73e66d7252020c31e50e0a5fedc00b343676e0ec1075399312/jq-1.8.0-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:e14aa012606470d1a21fdc39835b8eef395f7ea143c720940a48156de94752e9", size = 401438, upload-time = "2024-08-17T08:16:56.721Z" }, + { url = "https://files.pythonhosted.org/packages/95/3f/9f840980d6390b7eacb2a1d3e17c1edf9b0757571c93f801c48f5f494c58/jq-1.8.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:353db01bbb964eff9e39c8966e7c123cbdad1ff59cc3bee773a7a2034e2b843b", size = 410079, upload-time = "2024-08-17T08:16:59.248Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2e/70c61f02fc6307bcb2e079c8aa950eba9caf654c52473955d541261cf091/jq-1.8.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325480cba94f504b282f42912a16b32d94dd1e6347cf3a367ec3c97fe1dd1b3a", size = 409938, upload-time = "2024-08-17T08:17:01.476Z" }, + { url = "https://files.pythonhosted.org/packages/ae/75/04cb177d21afdbe5e31e2e2e1ae9ef6df651dd5668187090121ca179d147/jq-1.8.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4a79e94c83ebde789ff54e609f19b1923b2f57b2bd17ccb4953713577d4c3dc", size = 424088, upload-time = "2024-08-17T08:17:03.846Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/07b8ca4cd626eca4491c9f055f406d9a45375d7fcb75a877cb25bc88f023/jq-1.8.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc7ebcc1037c8a82db30aff9177f17379bcc91734def09548e939326717fd82d", size = 435591, upload-time = "2024-08-17T08:17:06.22Z" }, +] + +[[package]] +name = "jsonpath-ng" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ply" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/86/08646239a313f895186ff0a4573452038eed8c86f54380b3ebac34d32fb2/jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c", size = 37838, upload-time = "2024-10-11T15:41:42.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/5a/73ecb3d82f8615f32ccdadeb9356726d6cae3a4bbc840b437ceb95708063/jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6", size = 30105, upload-time = "2024-11-20T17:58:30.418Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "libcst" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/de/df97a73343469c0b92ad0784248bdde79e417bb9540c229216bd81d0b086/libcst-1.7.0.tar.gz", hash = "sha256:a63f44ffa81292f183656234c7f2848653ff45c17d867db83c9335119e28aafa", size = 776707, upload-time = "2025-03-13T10:14:21.472Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/1f/dc9a6107d6a443b0c3416b6b92211870a582dbcd31458b2196e5b7f5ff05/libcst-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:340054c57abcd42953248af18ed278be651a03b1c2a1616f7e1f1ef90b6018ce", size = 2078951, upload-time = "2025-03-13T10:13:17.094Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2a/d59635eefb6298c2620cc1fcceea9b767d8530e2445f4827d3fa6709a4b5/libcst-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdae6e632d222d8db7cb98d7cecb45597c21b8e3841d0c98d4fca79c49dad04b", size = 2215733, upload-time = "2025-03-13T10:13:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ab/6f2c882683a012c8d086564851538d66fc8f152c5fdf58a046ee8a496a5f/libcst-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8f59f3472fe8c0f6e2fad457825ea2ccad8c4c713cca55a91ff2cbfa9bc03", size = 2317267, upload-time = "2025-03-13T10:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/32/07/3188262f649ad6bf4194166cacb5c68f902e747035471e8f4d475a92e166/libcst-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1560598f5c56681adbd32f4b08e9cffcd45a021921d1d784370a7d4d9a2fac11", size = 2405957, upload-time = "2025-03-13T10:13:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5a/0ce9d68e83640b96f0b5d56adfff816a4a1085cffe6e6869019d0fdf75cf/libcst-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cd5ab15b12a37f0e9994d8847d5670da936a93d98672c442a956fab34ea0c15", size = 2277162, upload-time = "2025-03-13T10:13:24.485Z" }, + { url = "https://files.pythonhosted.org/packages/8a/df/6875a4f296860de08cc795dd951e4c40784f246ef1d38277d6e448fbeb59/libcst-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5d5ba9314569865effd5baff3a58ceb2cced52228e181824759c68486a7ec8f4", size = 2397589, upload-time = "2025-03-13T10:13:25.957Z" }, + { url = "https://files.pythonhosted.org/packages/1b/74/71e54574fe406cdb5c42506cf5148c544940d1d722ef473647de04314aa6/libcst-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:3d2ec10015e86a4402c3d2084ede6c7c9268faea1ecb99592fe9e291c515aaa2", size = 2096214, upload-time = "2025-03-13T10:13:27.404Z" }, + { url = "https://files.pythonhosted.org/packages/e8/42/5f21d245f0f2e4a4a47c977f6da4e46dca0ef0307f8a8cd4d0d85b0e08c8/libcst-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f6e693281d6e9a62414205fb300ec228ddc902ca9cb965a09f11561dc10aa94", size = 2078827, upload-time = "2025-03-13T10:13:29.15Z" }, + { url = "https://files.pythonhosted.org/packages/ac/62/ce81795b18bcc5bd77d32f53d56a79545cc3efe27acdd2ed107775c3fd18/libcst-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e635eadb6043d5f967450af27125811c6ccc7eeb4d8c5fd4f1bece9d96418781", size = 2215601, upload-time = "2025-03-13T10:13:30.994Z" }, + { url = "https://files.pythonhosted.org/packages/e3/89/c40a3cacf89b7dfd1f7c46abbec4cae00b85bd8968b1770c49e829a0dd23/libcst-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c568e14d29489f09faf4915af18235f805d5aa60fa194023b4fadf3209f0c94", size = 2316834, upload-time = "2025-03-13T10:13:32.469Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ff/9cef48453900919ce149906ef5532205d03ae7a3c5600ec10d621c656387/libcst-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9add619a825d6f176774110d79dc3137f353a236c1e3bcd6e063ca6d93d6e0ae", size = 2405859, upload-time = "2025-03-13T10:13:33.906Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/7159dac46efd48370c3c707144b1a451aec6317e71f989b3d6208c862f4e/libcst-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:57a6bcfc8ca8a0bb9e89a2dbf63ee8f0c7e8353a130528dcb47c9e59c2dc8c94", size = 2277105, upload-time = "2025-03-13T10:13:36.224Z" }, + { url = "https://files.pythonhosted.org/packages/f3/0b/7363d238a67d4623b01e3b61884db5883e71864ec7421bfc7bcb27efd662/libcst-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5e22738ec2855803f8242e6bf78057389d10f8954db34bf7079c82abab1b8b95", size = 2397105, upload-time = "2025-03-13T10:13:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/fde43c36ad65f5c997be7fe7c32c1951c6e881aee071a531c50f369e4f53/libcst-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa519d4391326329f37860c2f2aaf80cb11a6122d14afa2f4f00dde6fcfa7ae4", size = 2096252, upload-time = "2025-03-13T10:13:39.643Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ef/0e71046efefe6a68857645f1ff70e89e0d3c5a138c7bc8d766d3e10127af/libcst-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b52692a28d0d958ebfabcf8bfce5fcf2c8582967310d35e6111a6e2d4db96659", size = 2071234, upload-time = "2025-03-13T10:13:41.539Z" }, + { url = "https://files.pythonhosted.org/packages/fa/99/61380320d7f6ff9bf142ff195c0a6586152bf5ebd016bdf2a32063c602d5/libcst-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61bfc90c8a4594296f8b68702f494dfdfec6e745a4abc0cfa8069d7f22061424", size = 2210153, upload-time = "2025-03-13T10:13:42.943Z" }, + { url = "https://files.pythonhosted.org/packages/a6/58/1b4ebd4e8af3aaf460287ba5afc3e95fb5fc7ca2bdde1857373183a08516/libcst-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9370c23a3f609280c3f2296d61d34dd32afd7a1c9b19e4e29cc35cb2e2544363", size = 2312359, upload-time = "2025-03-13T10:13:44.514Z" }, + { url = "https://files.pythonhosted.org/packages/75/a4/8f182a64757ea6a2398e166b058d91002724feb340e7ec67119f2b2a43ca/libcst-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e50e6960ecc3ed67f39fec63aa329e772d5d27f8e2334e30f19a94aa14489f1", size = 2401963, upload-time = "2025-03-13T10:13:46.585Z" }, + { url = "https://files.pythonhosted.org/packages/07/48/0b5e5b0d43093859b97504f3f7a61cf4dc8a56e0997e62a573bdd2b4e2a2/libcst-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ca4e91aa854758040fa6fe7036fbe7f90a36a7d283fa1df8587b6f73084fc997", size = 2272088, upload-time = "2025-03-13T10:13:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/e2/2a5497cde7ad82ef41277cadd560ec1726e00d317dad85704327071d2b67/libcst-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d894c48f682b0061fdb2c983d5e64c30334db6ce0783560dbbb9df0163179c0c", size = 2392441, upload-time = "2025-03-13T10:13:49.781Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/dba943bc53bd688895dbc81918fc93ada02b4c3e5755faeecf4333878dd0/libcst-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:14e5c1d427c33d50df75be6bc999a7b2d7c6b7840e2361a18a6f354db50cb18e", size = 2094954, upload-time = "2025-03-13T10:13:51.858Z" }, + { url = "https://files.pythonhosted.org/packages/63/43/bd2b3b404219be09a791fc0d98910d09c36662f805d23e3b81600b80de0c/libcst-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93417d36c2a1b70d651d0e970ff73339e8dcd64d341672b68823fa0039665022", size = 2071262, upload-time = "2025-03-13T10:13:53.321Z" }, + { url = "https://files.pythonhosted.org/packages/05/27/428da06f863ebdca7f3908190e2a70c5cb5830c9efd5e1ea9b8c18c807bf/libcst-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6523731bfbdbc045ff8649130fe14a46b31ad6925f67acdc0e0d80a0c61719fd", size = 2210117, upload-time = "2025-03-13T10:13:55.166Z" }, + { url = "https://files.pythonhosted.org/packages/45/ff/24a82c2795fe846d07a43cda77e51acb5c9e6f57191b9f8607b5557234b0/libcst-1.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a252fa03ea00986f03100379f11e15d381103a09667900fb0fa2076cec19081a", size = 2312240, upload-time = "2025-03-13T10:13:56.653Z" }, + { url = "https://files.pythonhosted.org/packages/64/fd/97c695b706a6bc10e54b52eb8735cc9c7573afafdd15014dd1508885652d/libcst-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a5530b40a15dbe6fac842ef2ad87ad561760779380ccf3ade6850854d81406", size = 2402028, upload-time = "2025-03-13T10:13:58.132Z" }, + { url = "https://files.pythonhosted.org/packages/b0/72/eebf3bf6b47d2252eb9de4f1ec64706dcc90a4c12336b415c9a4f29cf54d/libcst-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0456381c939169c4f11caecdb30f7aca6f234640731f8f965849c1631930536b", size = 2272115, upload-time = "2025-03-13T10:14:00.023Z" }, + { url = "https://files.pythonhosted.org/packages/56/b3/5b76bfe1e02490a0c71b2ac05e236f1455192e1782e5f06bab4dca3501ea/libcst-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c8d6176a667d2db0132d133dad6bbf965f915f3071559342ca2cdbbec537ed12", size = 2392216, upload-time = "2025-03-13T10:14:01.871Z" }, + { url = "https://files.pythonhosted.org/packages/57/9a/535a81bade997f98bc17c151b524c00eb12a6738e9cbaecea00fbcccb6b9/libcst-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:6137fe549bfbb017283c3cf85419eb0dfaa20a211ad6d525538a2494e248a84b", size = 2094937, upload-time = "2025-03-13T10:14:03.452Z" }, +] + +[[package]] +name = "lxml" +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/1f/a3b6b74a451ceb84b471caa75c934d2430a4d84395d38ef201d539f38cd1/lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c", size = 8076838, upload-time = "2025-04-23T01:44:29.325Z" }, + { url = "https://files.pythonhosted.org/packages/36/af/a567a55b3e47135b4d1f05a1118c24529104c003f95851374b3748139dc1/lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7", size = 4381827, upload-time = "2025-04-23T01:44:33.345Z" }, + { url = "https://files.pythonhosted.org/packages/50/ba/4ee47d24c675932b3eb5b6de77d0f623c2db6dc466e7a1f199792c5e3e3a/lxml-5.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf", size = 5204098, upload-time = "2025-04-23T01:44:35.809Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0f/b4db6dfebfefe3abafe360f42a3d471881687fd449a0b86b70f1f2683438/lxml-5.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28", size = 4930261, upload-time = "2025-04-23T01:44:38.271Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/0bb1bae1ce056910f8db81c6aba80fec0e46c98d77c0f59298c70cd362a3/lxml-5.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609", size = 5529621, upload-time = "2025-04-23T01:44:40.921Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/e7b66a533fc4a1e7fa63dd22a1ab2ec4d10319b909211181e1ab3e539295/lxml-5.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4", size = 4983231, upload-time = "2025-04-23T01:44:43.871Z" }, + { url = "https://files.pythonhosted.org/packages/11/39/a38244b669c2d95a6a101a84d3c85ba921fea827e9e5483e93168bf1ccb2/lxml-5.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7", size = 5084279, upload-time = "2025-04-23T01:44:46.632Z" }, + { url = "https://files.pythonhosted.org/packages/db/64/48cac242347a09a07740d6cee7b7fd4663d5c1abd65f2e3c60420e231b27/lxml-5.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f", size = 4927405, upload-time = "2025-04-23T01:44:49.843Z" }, + { url = "https://files.pythonhosted.org/packages/98/89/97442835fbb01d80b72374f9594fe44f01817d203fa056e9906128a5d896/lxml-5.4.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997", size = 5550169, upload-time = "2025-04-23T01:44:52.791Z" }, + { url = "https://files.pythonhosted.org/packages/f1/97/164ca398ee654eb21f29c6b582685c6c6b9d62d5213abc9b8380278e9c0a/lxml-5.4.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c", size = 5062691, upload-time = "2025-04-23T01:44:56.108Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bc/712b96823d7feb53482d2e4f59c090fb18ec7b0d0b476f353b3085893cda/lxml-5.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b", size = 5133503, upload-time = "2025-04-23T01:44:59.222Z" }, + { url = "https://files.pythonhosted.org/packages/d4/55/a62a39e8f9da2a8b6002603475e3c57c870cd9c95fd4b94d4d9ac9036055/lxml-5.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b", size = 4999346, upload-time = "2025-04-23T01:45:02.088Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/a393728ae001b92bb1a9e095e570bf71ec7f7fbae7688a4792222e56e5b9/lxml-5.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563", size = 5627139, upload-time = "2025-04-23T01:45:04.582Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5f/9dcaaad037c3e642a7ea64b479aa082968de46dd67a8293c541742b6c9db/lxml-5.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5", size = 5465609, upload-time = "2025-04-23T01:45:07.649Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/ebcae89edf27e61c45023005171d0ba95cb414ee41c045ae4caf1b8487fd/lxml-5.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776", size = 5192285, upload-time = "2025-04-23T01:45:10.456Z" }, + { url = "https://files.pythonhosted.org/packages/42/ad/cc8140ca99add7d85c92db8b2354638ed6d5cc0e917b21d36039cb15a238/lxml-5.4.0-cp310-cp310-win32.whl", hash = "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7", size = 3477507, upload-time = "2025-04-23T01:45:12.474Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/597ce090da1097d2aabd2f9ef42187a6c9c8546d67c419ce61b88b336c85/lxml-5.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250", size = 3805104, upload-time = "2025-04-23T01:45:15.104Z" }, + { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" }, + { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" }, + { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" }, + { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" }, + { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" }, + { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" }, + { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" }, + { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" }, + { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" }, + { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" }, + { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" }, + { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" }, + { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" }, + { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" }, + { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" }, + { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" }, + { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" }, + { url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" }, + { url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" }, + { url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" }, + { url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" }, + { url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" }, + { url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" }, + { url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" }, + { url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" }, + { url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b0/e4d1cbb8c078bc4ae44de9c6a79fec4e2b4151b1b4d50af71d799e76b177/lxml-5.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55", size = 3892319, upload-time = "2025-04-23T01:49:22.069Z" }, + { url = "https://files.pythonhosted.org/packages/5b/aa/e2bdefba40d815059bcb60b371a36fbfcce970a935370e1b367ba1cc8f74/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740", size = 4211614, upload-time = "2025-04-23T01:49:24.599Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/91ff89d1e092e7cfdd8453a939436ac116db0a665e7f4be0cd8e65c7dc5a/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5", size = 4306273, upload-time = "2025-04-23T01:49:27.355Z" }, + { url = "https://files.pythonhosted.org/packages/be/7c/8c3f15df2ca534589717bfd19d1e3482167801caedfa4d90a575facf68a6/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37", size = 4208552, upload-time = "2025-04-23T01:49:29.949Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/9567afb1665f64d73fc54eb904e418d1138d7f011ed00647121b4dd60b38/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571", size = 4331091, upload-time = "2025-04-23T01:49:32.842Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ab/fdbbd91d8d82bf1a723ba88ec3e3d76c022b53c391b0c13cad441cdb8f9e/lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", size = 3487862, upload-time = "2025-04-23T01:49:36.296Z" }, +] + +[[package]] +name = "mando" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/24/cd70d5ae6d35962be752feccb7dca80b5e0c2d450e995b16abd6275f3296/mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500", size = 37868, upload-time = "2022-02-24T08:12:27.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a", size = 28149, upload-time = "2022-02-24T08:12:25.24Z" }, +] + +[[package]] +name = "markdown" +version = "3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906, upload-time = "2025-04-11T14:42:50.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210, upload-time = "2025-04-11T14:42:49.178Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825, upload-time = "2025-02-03T15:32:25.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, +] + +[[package]] +name = "marshmallow-enum" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/8c/ceecdce57dfd37913143087fffd15f38562a94f0d22823e3c66eac0dca31/marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", size = 4013, upload-time = "2019-08-21T01:07:46.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/ef3a3dc499be447098d4a89399beb869f813fee1b5a57d5d79dee2c1bf51/marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072", size = 4186, upload-time = "2019-08-21T01:07:44.814Z" }, +] + +[[package]] +name = "mbstrdecoder" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/ab/05ae008357c8bdb6245ebf8a101d99f26c096e0ea20800b318153da23796/mbstrdecoder-1.1.4.tar.gz", hash = "sha256:8105ef9cf6b7d7d69fe7fd6b68a2d8f281ca9b365d7a9b670be376b2e6c81b21", size = 14527, upload-time = "2025-01-18T10:07:31.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/ac/5ce64a1d4cce00390beab88622a290420401f1cabf05caf2fc0995157c21/mbstrdecoder-1.1.4-py3-none-any.whl", hash = "sha256:03dae4ec50ec0d2ff4743e63fdbd5e0022815857494d35224b60775d3d934a8c", size = 7933, upload-time = "2025-01-18T10:07:29.562Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mcp" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/97/0a3e08559557b0ac5799f9fb535fbe5a4e4dcdd66ce9d32e7a74b4d0534d/mcp-1.8.0.tar.gz", hash = "sha256:263dfb700540b726c093f0c3e043f66aded0730d0b51f04eb0a3eb90055fe49b", size = 264641, upload-time = "2025-05-08T20:09:06.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b2/4ac3bd17b1fdd65658f18de4eb0c703517ee0b483dc5f56467802a9197e0/mcp-1.8.0-py3-none-any.whl", hash = "sha256:889d9d3b4f12b7da59e7a3933a0acadae1fce498bfcd220defb590aa291a1334", size = 119544, upload-time = "2025-05-08T20:09:04.458Z" }, +] + +[[package]] +name = "mcpgateway" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "aiofiles" }, + { name = "fastapi" }, + { name = "gunicorn" }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "jq" }, + { name = "jsonpath-ng" }, + { name = "jsonschema" }, + { name = "mcp" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "sqlalchemy" }, + { name = "sse-starlette" }, + { name = "uvicorn" }, +] + +[package.optional-dependencies] +dev = [ + { name = "argparse-manpage" }, + { name = "autoflake" }, + { name = "bandit" }, + { name = "black" }, + { name = "bump2version" }, + { name = "code2flow" }, + { name = "cookiecutter" }, + { name = "coverage" }, + { name = "coverage-badge" }, + { name = "darglint" }, + { name = "fawltydeps" }, + { name = "flake8" }, + { name = "gprof2dot" }, + { name = "handsdown" }, + { name = "importchecker" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pexpect" }, + { name = "pip-licenses" }, + { name = "pre-commit" }, + { name = "pydocstyle" }, + { name = "pylint" }, + { name = "pylint-pydantic" }, + { name = "pyre-check" }, + { name = "pyright" }, + { name = "pyroma" }, + { name = "pyspelling" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-examples" }, + { name = "pytest-md-report" }, + { name = "pytest-rerunfailures" }, + { name = "pytest-xdist" }, + { name = "pytype" }, + { name = "radon" }, + { name = "ruff" }, + { name = "settings-doc" }, + { name = "snakeviz" }, + { name = "types-tabulate" }, +] +prometheus = [ + { name = "prometheus-client" }, +] +redis = [ + { name = "redis" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiofiles", specifier = ">=24.1.0" }, + { name = "argparse-manpage", marker = "extra == 'dev'", specifier = ">=4.6" }, + { name = "autoflake", marker = "extra == 'dev'", specifier = ">=2.3.1" }, + { name = "bandit", marker = "extra == 'dev'", specifier = ">=1.8.3" }, + { name = "black", marker = "extra == 'dev'", specifier = ">=25.1.0" }, + { name = "bump2version", marker = "extra == 'dev'", specifier = ">=1.0.1" }, + { name = "code2flow", marker = "extra == 'dev'", specifier = ">=2.5.1" }, + { name = "cookiecutter", marker = "extra == 'dev'", specifier = ">=2.6.0" }, + { name = "coverage", marker = "extra == 'dev'", specifier = ">=7.8.0" }, + { name = "coverage-badge", marker = "extra == 'dev'", specifier = ">=1.1.2" }, + { name = "darglint", marker = "extra == 'dev'", specifier = ">=1.8.1" }, + { name = "fastapi", specifier = ">=0.115.12" }, + { name = "fawltydeps", marker = "extra == 'dev'", specifier = ">=0.19.0" }, + { name = "flake8", marker = "extra == 'dev'", specifier = ">=7.2.0" }, + { name = "gprof2dot", marker = "extra == 'dev'", specifier = ">=2025.4.14" }, + { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "handsdown", marker = "extra == 'dev'", specifier = ">=2.1.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "importchecker", marker = "extra == 'dev'", specifier = ">=3.0" }, + { name = "isort", marker = "extra == 'dev'", specifier = ">=6.0.1" }, + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "jq", specifier = ">=1.8.0" }, + { name = "jsonpath-ng", specifier = ">=1.7.0" }, + { name = "jsonschema", specifier = ">=4.23.0" }, + { name = "mcp", specifier = ">=1.8.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.15.0" }, + { name = "pexpect", marker = "extra == 'dev'", specifier = ">=4.9.0" }, + { name = "pip-licenses", marker = "extra == 'dev'", specifier = ">=5.0.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.2.0" }, + { name = "prometheus-client", marker = "extra == 'prometheus'", specifier = ">=0.21.1" }, + { name = "pydantic", specifier = ">=2.11.4" }, + { name = "pydantic-settings", specifier = ">=2.9.1" }, + { name = "pydocstyle", marker = "extra == 'dev'", specifier = ">=6.3.0" }, + { name = "pyjwt", extras = ["crypto"], specifier = ">=2.10.1" }, + { name = "pylint", marker = "extra == 'dev'", specifier = ">=3.3.7" }, + { name = "pylint-pydantic", marker = "extra == 'dev'", specifier = ">=0.3.5" }, + { name = "pyre-check", marker = "extra == 'dev'", specifier = ">=0.9.23" }, + { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.400" }, + { name = "pyroma", marker = "extra == 'dev'", specifier = ">=4.2" }, + { name = "pyspelling", marker = "extra == 'dev'", specifier = ">=2.10" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.5" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.26.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.1.1" }, + { name = "pytest-examples", marker = "extra == 'dev'", specifier = ">=0.0.18" }, + { name = "pytest-md-report", marker = "extra == 'dev'", specifier = ">=0.7.0" }, + { name = "pytest-rerunfailures", marker = "extra == 'dev'", specifier = ">=15.1" }, + { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1" }, + { name = "python-multipart", specifier = ">=0.0.20" }, + { name = "pytype", marker = "extra == 'dev'", specifier = ">=2024.10.11" }, + { name = "radon", marker = "extra == 'dev'", specifier = ">=6.0.1" }, + { name = "redis", marker = "extra == 'redis'", specifier = ">=6.0.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.11.9" }, + { name = "settings-doc", marker = "extra == 'dev'", specifier = ">=4.3.2" }, + { name = "snakeviz", marker = "extra == 'dev'", specifier = ">=2.2.2" }, + { name = "sqlalchemy", specifier = ">=2.0.40" }, + { name = "sse-starlette", specifier = ">=2.3.4" }, + { name = "types-tabulate", marker = "extra == 'dev'", specifier = ">=0.9.0.20241207" }, + { name = "uvicorn", specifier = ">=0.34.2" }, +] +provides-extras = ["dev", "redis", "prometheus"] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "msgspec" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934, upload-time = "2024-12-27T17:40:28.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/40/817282b42f58399762267b30deb8ac011d8db373f8da0c212c85fbe62b8f/msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259", size = 190019, upload-time = "2024-12-27T17:39:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/92/99/bd7ed738c00f223a8119928661167a89124140792af18af513e6519b0d54/msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36", size = 183680, upload-time = "2024-12-27T17:39:17.847Z" }, + { url = "https://files.pythonhosted.org/packages/e5/27/322badde18eb234e36d4a14122b89edd4e2973cdbc3da61ca7edf40a1ccd/msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947", size = 209334, upload-time = "2024-12-27T17:39:19.065Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/080509c5774a1592b2779d902a70b5fe008532759927e011f068145a16cb/msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909", size = 211551, upload-time = "2024-12-27T17:39:21.767Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2e/1c23c6b4ca6f4285c30a39def1054e2bee281389e4b681b5e3711bd5a8c9/msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a", size = 215099, upload-time = "2024-12-27T17:39:24.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/fe/95f9654518879f3359d1e76bc41189113aa9102452170ab7c9a9a4ee52f6/msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633", size = 218211, upload-time = "2024-12-27T17:39:27.396Z" }, + { url = "https://files.pythonhosted.org/packages/79/f6/71ca7e87a1fb34dfe5efea8156c9ef59dd55613aeda2ca562f122cd22012/msgspec-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90", size = 186174, upload-time = "2024-12-27T17:39:29.647Z" }, + { url = "https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e", size = 187939, upload-time = "2024-12-27T17:39:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551", size = 182202, upload-time = "2024-12-27T17:39:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7", size = 209029, upload-time = "2024-12-27T17:39:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011", size = 210682, upload-time = "2024-12-27T17:39:36.384Z" }, + { url = "https://files.pythonhosted.org/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063", size = 214003, upload-time = "2024-12-27T17:39:39.097Z" }, + { url = "https://files.pythonhosted.org/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716", size = 216833, upload-time = "2024-12-27T17:39:41.203Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c", size = 186184, upload-time = "2024-12-27T17:39:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485, upload-time = "2024-12-27T17:39:44.974Z" }, + { url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910, upload-time = "2024-12-27T17:39:46.401Z" }, + { url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633, upload-time = "2024-12-27T17:39:49.099Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594, upload-time = "2024-12-27T17:39:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053, upload-time = "2024-12-27T17:39:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081, upload-time = "2024-12-27T17:39:55.142Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467, upload-time = "2024-12-27T17:39:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86", size = 190498, upload-time = "2024-12-27T17:40:00.427Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314", size = 183950, upload-time = "2024-12-27T17:40:04.219Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e", size = 210647, upload-time = "2024-12-27T17:40:05.606Z" }, + { url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5", size = 213563, upload-time = "2024-12-27T17:40:10.516Z" }, + { url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9", size = 213996, upload-time = "2024-12-27T17:40:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327", size = 219087, upload-time = "2024-12-27T17:40:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f", size = 187432, upload-time = "2024-12-27T17:40:16.256Z" }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433, upload-time = "2025-02-05T03:49:29.145Z" }, + { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472, upload-time = "2025-02-05T03:49:16.986Z" }, + { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424, upload-time = "2025-02-05T03:49:46.908Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450, upload-time = "2025-02-05T03:50:05.89Z" }, + { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765, upload-time = "2025-02-05T03:49:33.56Z" }, + { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701, upload-time = "2025-02-05T03:49:38.981Z" }, + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "ninja" +version = "1.11.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/d4/6b0324541018561c5e73e617bd16f20a4fc17d1179bb3b3520b6ca8beb7b/ninja-1.11.1.4.tar.gz", hash = "sha256:6aa39f6e894e0452e5b297327db00019383ae55d5d9c57c73b04f13bf79d438a", size = 201256, upload-time = "2025-03-22T06:46:43.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/b1/3a61b348936b62a386465b1937cd778fa3a5748582e26d832dbab844ff27/ninja-1.11.1.4-py3-none-macosx_10_9_universal2.whl", hash = "sha256:b33923c8da88e8da20b6053e38deb433f53656441614207e01d283ad02c5e8e7", size = 279071, upload-time = "2025-03-22T06:46:17.806Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/4c94fdad51fcf1f039a156e97de9e4d564c2a8cc0303782d36f9bd893a4b/ninja-1.11.1.4-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cede0af00b58e27b31f2482ba83292a8e9171cdb9acc2c867a3b6e40b3353e43", size = 472026, upload-time = "2025-03-22T06:46:19.974Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7a/455d2877fe6cf99886849c7f9755d897df32eaf3a0fba47b56e615f880f7/ninja-1.11.1.4-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:096487995473320de7f65d622c3f1d16c3ad174797602218ca8c967f51ec38a0", size = 422814, upload-time = "2025-03-22T06:46:21.235Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ad/fb6cca942528e25e8e0ab0f0cf98fe007319bf05cf69d726c564b815c4af/ninja-1.11.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3090d4488fadf6047d0d7a1db0c9643a8d391f0d94729554dbb89b5bdc769d7", size = 156965, upload-time = "2025-03-22T06:46:23.45Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e7/d94a1b60031b115dd88526834b3da69eaacdc3c1a6769773ca8e2b1386b5/ninja-1.11.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecce44a00325a93631792974659cf253a815cc6da4ec96f89742925dfc295a0d", size = 179937, upload-time = "2025-03-22T06:46:24.728Z" }, + { url = "https://files.pythonhosted.org/packages/08/cc/e9316a28235409e9363794fc3d0b3083e48dd80d441006de66421e55f364/ninja-1.11.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c29bb66d2aa46a2409ab369ea804c730faec7652e8c22c1e428cc09216543e5", size = 157020, upload-time = "2025-03-22T06:46:26.046Z" }, + { url = "https://files.pythonhosted.org/packages/e3/30/389b22300541aa5f2e9dad322c4de2f84be4e32aa4e8babd9160d620b5f1/ninja-1.11.1.4-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:055f386fb550c2c9d6157e45e20a84d29c47968876b9c5794ae2aec46f952306", size = 130389, upload-time = "2025-03-22T06:46:27.174Z" }, + { url = "https://files.pythonhosted.org/packages/a9/10/e27f35cb92813aabbb7ae771b1685b45be1cc8a0798ce7d4bfd08d142b93/ninja-1.11.1.4-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:f6186d7607bb090c3be1e10c8a56b690be238f953616626f5032238c66e56867", size = 372435, upload-time = "2025-03-22T06:46:28.637Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/e3559619756739aae124c6abf7fe41f7e546ab1209cfbffb13137bff2d2e/ninja-1.11.1.4-py3-none-musllinux_1_1_i686.whl", hash = "sha256:cf4453679d15babc04ba023d68d091bb613091b67101c88f85d2171c6621c6eb", size = 419300, upload-time = "2025-03-22T06:46:30.392Z" }, + { url = "https://files.pythonhosted.org/packages/35/46/809e4e9572570991b8e6f88f3583807d017371ab4cb09171cbc72a7eb3e4/ninja-1.11.1.4-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:d4a6f159b08b0ac4aca5ee1572e3e402f969139e71d85d37c0e2872129098749", size = 420239, upload-time = "2025-03-22T06:46:32.442Z" }, + { url = "https://files.pythonhosted.org/packages/e6/64/5cb5710d15f844edf02ada577f8eddfdcd116f47eec15850f3371a3a4b33/ninja-1.11.1.4-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:c3b96bd875f3ef1db782470e9e41d7508905a0986571f219d20ffed238befa15", size = 415986, upload-time = "2025-03-22T06:46:33.821Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/0e9ab1d926f423b12b09925f78afcc5e48b3c22e7121be3ddf6c35bf06a3/ninja-1.11.1.4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:cf554e73f72c04deb04d0cf51f5fdb1903d9c9ca3d2344249c8ce3bd616ebc02", size = 379657, upload-time = "2025-03-22T06:46:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/3e/fd6d330d0434168e7fe070d414b57dd99c4c133faa69c05b42a3cbdc6c13/ninja-1.11.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cfdd09776436a1ff3c4a2558d3fc50a689fb9d7f1bdbc3e6f7b8c2991341ddb3", size = 454466, upload-time = "2025-03-22T06:46:37.413Z" }, + { url = "https://files.pythonhosted.org/packages/e6/df/a25f3ad0b1c59d1b90564096e4fd89a6ca30d562b1e942f23880c3000b89/ninja-1.11.1.4-py3-none-win32.whl", hash = "sha256:2ab67a41c90bea5ec4b795bab084bc0b3b3bb69d3cd21ca0294fc0fc15a111eb", size = 255931, upload-time = "2025-03-22T06:46:39.171Z" }, + { url = "https://files.pythonhosted.org/packages/5b/10/9b8fe9ac004847490cc7b54896124c01ce2d87d95dc60aabd0b8591addff/ninja-1.11.1.4-py3-none-win_amd64.whl", hash = "sha256:4617b3c12ff64b611a7d93fd9e378275512bb36eff8babff7c83f5116b4f8d66", size = 296461, upload-time = "2025-03-22T06:46:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/b9/58/612a17593c2d117f96c7f6b7f1e6570246bddc4b1e808519403a1417f217/ninja-1.11.1.4-py3-none-win_arm64.whl", hash = "sha256:5713cf50c5be50084a8693308a63ecf9e55c3132a78a41ab1363a28b6caaaee1", size = 271441, upload-time = "2025-03-22T06:46:42.147Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pathvalidate" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/87/c7a2f51cc62df0495acb0ed2533a7c74cc895e569a1b020ee5f6e9fa4e21/pathvalidate-3.2.3.tar.gz", hash = "sha256:59b5b9278e30382d6d213497623043ebe63f10e29055be4419a9c04c721739cb", size = 61717, upload-time = "2025-01-03T14:06:42.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/14/c5a0e1a947909810fc4c043b84cac472b70e438148d34f5393be1bac663f/pathvalidate-3.2.3-py3-none-any.whl", hash = "sha256:5eaf0562e345d4b6d0c0239d0f690c3bd84d2a9a3c4c73b99ea667401b27bee1", size = 24130, upload-time = "2025-01-03T14:06:39.568Z" }, +] + +[[package]] +name = "pbr" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/d2/510cc0d218e753ba62a1bc1434651db3cd797a9716a0a66cc714cb4f0935/pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b", size = 125702, upload-time = "2025-02-04T14:28:06.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/ac/684d71315abc7b1214d59304e23a982472967f6bf4bde5a98f1503f648dc/pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76", size = 108997, upload-time = "2025-02-04T14:28:03.168Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pip" +version = "25.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/de/241caa0ca606f2ec5fe0c1f4261b0465df78d786a38da693864a116c37f4/pip-25.1.1.tar.gz", hash = "sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077", size = 1940155, upload-time = "2025-05-02T15:14:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl", hash = "sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af", size = 1825227, upload-time = "2025-05-02T15:13:59.102Z" }, +] + +[[package]] +name = "pip-licenses" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prettytable" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/49/d36a3ddb73d22970a35afa3e9fd53c8318150f8122e4257ca9875f1d4e38/pip_licenses-5.0.0.tar.gz", hash = "sha256:0633a1f9aab58e5a6216931b0e1d5cdded8bcc2709ff563674eb0e2ff9e77e8e", size = 41542, upload-time = "2024-07-23T10:48:29.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/0a/bfaf1479d09d19f503a669d9c8e433ac59ae687fb8da1d8207eb85c5a9f4/pip_licenses-5.0.0-py3-none-any.whl", hash = "sha256:82c83666753efb86d1af1c405c8ab273413eb10d6689c218df2f09acf40e477d", size = 20497, upload-time = "2024-07-23T10:48:27.59Z" }, +] + +[[package]] +name = "pip-requirements-parser" +version = "32.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/2a/63b574101850e7f7b306ddbdb02cb294380d37948140eecd468fae392b54/pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3", size = 209359, upload-time = "2022-12-21T15:25:22.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/d0/d04f1d1e064ac901439699ee097f58688caadea42498ec9c4b4ad2ef84ab/pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526", size = 35648, upload-time = "2022-12-21T15:25:21.046Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "ply" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, +] + +[[package]] +name = "prettytable" +version = "3.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/b1/85e18ac92afd08c533603e3393977b6bc1443043115a47bb094f3b98f94f/prettytable-3.16.0.tar.gz", hash = "sha256:3c64b31719d961bf69c9a7e03d0c1e477320906a98da63952bc6698d6164ff57", size = 66276, upload-time = "2025-03-24T19:39:04.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl", hash = "sha256:b5eccfabb82222f5aa46b798ff02a8452cf530a352c31bddfa29be41242863aa", size = 33863, upload-time = "2025-03-24T19:39:02.359Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551, upload-time = "2024-12-03T14:59:12.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682, upload-time = "2024-12-03T14:59:10.935Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pycnite" +version = "2024.7.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/55/7f412c01675a8c77a9ace64a37d557faebe6740cf7fd619c9bda82b33341/pycnite-2024.7.31.tar.gz", hash = "sha256:5125f1c95aef4a23b9bec3b32fae76873dcd46324fa68e39c10fa852ecdea340", size = 24189, upload-time = "2024-07-31T12:43:15.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/af/7ba371f966657f6e7b1c9876cae7e9f1c5d3635c3df1329636b99e615494/pycnite-2024.7.31-py3-none-any.whl", hash = "sha256:9ff9c09d35056435b867e14ebf79626ca94b6017923a0bf9935377fa90d4cbb3", size = 22939, upload-time = "2024-07-31T12:43:14.248Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, +] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snowballstemmer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796, upload-time = "2023-01-17T20:29:19.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/ea/99ddefac41971acad68f14114f38261c1f27dac0b3ec529824ebc739bdaa/pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", size = 38038, upload-time = "2023-01-17T20:29:18.094Z" }, +] + +[[package]] +name = "pydot" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/c3/6034ed1ebf2e3ba95a0e35fa7c43104e40444c0ed2b5325702c63e824dbf/pydot-4.0.0.tar.gz", hash = "sha256:12f16493337cade2f7631b87c8ccd299ba2e251f3ee5d0732a058df2887afe97", size = 161793, upload-time = "2025-05-04T11:13:03.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/16/984c0cf5073a23154b1f95c9d131b14c9fea83bfadae4ba8fc169daded11/pydot-4.0.0-py3-none-any.whl", hash = "sha256:cf86e13a6cfe2a96758a9702537f77e0ac1368db8ef277b4d3b34473ea425c97", size = 37535, upload-time = "2025-05-04T11:13:01.458Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pylint" +version = "3.3.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/e4/83e487d3ddd64ab27749b66137b26dc0c5b5c161be680e6beffdc99070b3/pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", size = 1520709, upload-time = "2025-05-04T17:07:51.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/83/bff755d09e31b5d25cc7fdc4bf3915d1a404e181f1abf0359af376845c24/pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d", size = 522565, upload-time = "2025-05-04T17:07:48.714Z" }, +] + +[[package]] +name = "pylint-plugin-utils" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pylint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/d2/3b9728910bc69232ec38d8fb7053c03c887bfe7e6e170649b683dd351750/pylint_plugin_utils-0.8.2.tar.gz", hash = "sha256:d3cebf68a38ba3fba23a873809155562571386d4c1b03e5b4c4cc26c3eee93e4", size = 10674, upload-time = "2023-05-20T04:42:16.154Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/ee/49d11aee31061bcc1d2726bd8334a2883ddcdbde7d7744ed6b3bd11704ed/pylint_plugin_utils-0.8.2-py3-none-any.whl", hash = "sha256:ae11664737aa2effbf26f973a9e0b6779ab7106ec0adc5fe104b0907ca04e507", size = 11171, upload-time = "2023-05-20T04:42:13.65Z" }, +] + +[[package]] +name = "pylint-pydantic" +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "pylint" }, + { name = "pylint-plugin-utils" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/b6/57b898006cb358af02b6a5b84909630630e89b299e7f9fc2dc7b3f0b61ef/pylint_pydantic-0.3.5-py3-none-any.whl", hash = "sha256:e7a54f09843b000676633ed02d5985a4a61c8da2560a3b0d46082d2ff171c4a1", size = 16139, upload-time = "2025-01-07T01:38:07.614Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pyre-check" +version = "0.9.23" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "dataclasses-json" }, + { name = "libcst" }, + { name = "psutil" }, + { name = "pyre-extensions" }, + { name = "tabulate" }, + { name = "testslide" }, + { name = "typing-extensions" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/dd3da9b3d1798067e72e52824b67eaea405eae65a5de4b3a0a0dd2fbee9a/pyre-check-0.9.23.tar.gz", hash = "sha256:3f4baf99145e06af416a2444e50b9e90b183585c053ab476004729ed9ba6902c", size = 22706217, upload-time = "2024-10-29T21:10:56.839Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/aa/6e93595136eeb35fd30da0e6fc445364d0cda64c298caa619f7cc6327b8d/pyre_check-0.9.23-py3-none-macosx_10_11_x86_64.whl", hash = "sha256:71ae076a75293a6fbb9025c3aa1e7a81a4dfd7a6da8a884f4c39deed2e4e3f3a", size = 23432827, upload-time = "2024-10-29T21:10:52.339Z" }, + { url = "https://files.pythonhosted.org/packages/b2/61/7d8793cdcd3ecf64452b6b926e4bcdb5e32f0d15624131414566badb7936/pyre_check-0.9.23-py3-none-manylinux1_x86_64.whl", hash = "sha256:6362f0d8af2d513c90fc863a142009d8d7cbf0aa762ec37cad194684bd962ae5", size = 47944875, upload-time = "2024-10-29T21:10:48.207Z" }, +] + +[[package]] +name = "pyre-extensions" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/53/5bc2532536e921c48366ad1047c1344ccef6afa5e84053f0f6e20a453767/pyre_extensions-0.0.32.tar.gz", hash = "sha256:5396715f14ea56c4d5fd0a88c57ca7e44faa468f905909edd7de4ad90ed85e55", size = 10852, upload-time = "2024-11-22T19:26:44.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/7a/9812cb8be9828ab688203c5ac5f743c60652887f0c00995a6f6f19f912bd/pyre_extensions-0.0.32-py3-none-any.whl", hash = "sha256:a63ba6883ab02f4b1a9f372ed4eb4a2f4c6f3d74879aa2725186fdfcfe3e5c68", size = 12766, upload-time = "2024-11-22T19:26:42.465Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.400" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/cb/c306618a02d0ee8aed5fb8d0fe0ecfed0dbf075f71468f03a30b5f4e1fe0/pyright-1.1.400.tar.gz", hash = "sha256:b8a3ba40481aa47ba08ffb3228e821d22f7d391f83609211335858bf05686bdb", size = 3846546, upload-time = "2025-04-24T12:55:18.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/a5/5d285e4932cf149c90e3c425610c5efaea005475d5f96f1bfdb452956c62/pyright-1.1.400-py3-none-any.whl", hash = "sha256:c80d04f98b5a4358ad3a35e241dbf2a408eee33a40779df365644f8054d2517e", size = 5563460, upload-time = "2025-04-24T12:55:17.002Z" }, +] + +[[package]] +name = "pyroma" +version = "4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "build" }, + { name = "docutils" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/d2/4b9a4c1c5209318601735e1f42c49c10150e04c4a50cf1001a88a28278d3/pyroma-4.2.tar.gz", hash = "sha256:6c727dc4a7a10e12274faed5fb47ebd499ca0821995befec98e3cfcaf1e7383c", size = 97438, upload-time = "2023-02-25T12:57:39.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b2/83258f88658c6d7749cb5be7a8cbf1aeb188b76484b243f8b52b41d3a546/pyroma-4.2-py3-none-any.whl", hash = "sha256:a59854b6f8a72b55384cc1de42410e5c5ac59d0c40a92e84fd8364aa6cec3e37", size = 22440, upload-time = "2023-02-25T12:57:36.688Z" }, +] + +[[package]] +name = "pyspelling" +version = "2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "html5lib" }, + { name = "lxml" }, + { name = "markdown" }, + { name = "pyyaml" }, + { name = "soupsieve" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/07/168a857755a29b7e41550a28cd8f527025bc62fcb36a951d8f3f2eedcdf7/pyspelling-2.10.tar.gz", hash = "sha256:acd67133c1b7cecd410e3d4489e61f2e4b1f0b6acf1ae6c48c240fbb21729c37", size = 148239, upload-time = "2024-01-13T06:08:26.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/16/242558b5c5cb73efd52490f1e6bfb03eae63b2585770b9cae78bd491ef0b/pyspelling-2.10-py3-none-any.whl", hash = "sha256:9b079dd238bd0616a49f9ac5df32799beb851dddc5ed7634f551e7df1aeee943", size = 45035, upload-time = "2024-01-13T06:08:23.641Z" }, +] + +[[package]] +name = "pytablewriter" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dataproperty" }, + { name = "mbstrdecoder" }, + { name = "pathvalidate" }, + { name = "setuptools" }, + { name = "tabledata" }, + { name = "tcolorpy" }, + { name = "typepy", extra = ["datetime"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/a1/617730f290f04d347103ab40bf67d317df6691b14746f6e1ea039fb57062/pytablewriter-1.2.1.tar.gz", hash = "sha256:7bd0f4f397e070e3b8a34edcf1b9257ccbb18305493d8350a5dbc9957fced959", size = 619241, upload-time = "2025-01-01T15:37:00.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/4c/c199512f01c845dfe5a7840ab3aae6c60463b5dc2a775be72502dfd9170a/pytablewriter-1.2.1-py3-none-any.whl", hash = "sha256:e906ff7ff5151d70a5f66e0f7b75642a7f2dce8d893c265b79cc9cf6bc04ddb4", size = 91083, upload-time = "2025-01-01T15:36:55.63Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload-time = "2025-03-25T06:22:28.883Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload-time = "2025-03-25T06:22:27.807Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, +] + +[[package]] +name = "pytest-examples" +version = "0.0.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "black" }, + { name = "pytest" }, + { name = "ruff" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/71/4ae972fd95f474454aa450108ee1037830e7ba11840363e981b8d48fd16a/pytest_examples-0.0.18.tar.gz", hash = "sha256:9a464f007f805b113677a15e2f8942ebb92d7d3eb5312e9a405d018478ec9801", size = 21237, upload-time = "2025-05-06T07:46:10.705Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/52/7bbfb6e987d9a8a945f22941a8da63e3529465f1b106ef0e26f5df7c780d/pytest_examples-0.0.18-py3-none-any.whl", hash = "sha256:86c195b98c4e55049a0df3a0a990ca89123b7280473ab57608eecc6c47bcfe9c", size = 18169, upload-time = "2025-05-06T07:46:09.349Z" }, +] + +[[package]] +name = "pytest-md-report" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytablewriter" }, + { name = "pytest" }, + { name = "tcolorpy" }, + { name = "typepy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/63/80d92406f952eee7856114c18ad269192ce179d576343fbcf0679c9a2bdd/pytest_md_report-0.7.0.tar.gz", hash = "sha256:3b832eaf660b470b5742e58d9c9a5e312b0712a7012d251cc04e908a81ce3c96", size = 284275, upload-time = "2025-05-02T03:07:09.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/96/6125a1a963b3864b4a3981c9fce27df60370a889c2e79071e33d325f14d9/pytest_md_report-0.7.0-py3-none-any.whl", hash = "sha256:90ccb3b5b9587d064ec83974db34619addd11d3de6ec7056fab4feb3af69ae94", size = 14250, upload-time = "2025-05-02T03:07:06.539Z" }, +] + +[[package]] +name = "pytest-rerunfailures" +version = "15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/78/e6e358545537a8e82c4dc91e72ec0d6f80546a3786dd27c76b06ca09db77/pytest_rerunfailures-15.1.tar.gz", hash = "sha256:c6040368abd7b8138c5b67288be17d6e5611b7368755ce0465dda0362c8ece80", size = 26981, upload-time = "2025-05-08T06:36:33.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/30/11d836ff01c938969efa319b4ebe2374ed79d28043a12bfc908577aab9f3/pytest_rerunfailures-15.1-py3-none-any.whl", hash = "sha256:f674c3594845aba8b23c78e99b1ff8068556cc6a8b277f728071fdc4f4b0b355", size = 13274, upload-time = "2025-05-08T06:36:32.029Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + +[[package]] +name = "pytype" +version = "2024.10.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "immutabledict" }, + { name = "importlab" }, + { name = "jinja2" }, + { name = "libcst" }, + { name = "msgspec" }, + { name = "networkx" }, + { name = "ninja" }, + { name = "pycnite" }, + { name = "pydot" }, + { name = "tabulate" }, + { name = "toml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/7a/6fd33673f9c7b9e5f4f8107028c323b1c72acc0f909f1b9b3391a31ea604/pytype-2024.10.11.tar.gz", hash = "sha256:ae5ff82f0b07d5ad68d4ec32a3e8de44fad6ed565a821a76aca50a14df382274", size = 2789497, upload-time = "2024-10-11T08:28:46.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/18/ae46cb01ff6c95bfef5bb2a8a9e089dff537972999a02d9ee7674c98de7b/pytype-2024.10.11-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:1c5a43b132b19928a38ba1dbcf8f4e3f67a41ea26087ccf26ae371c4076c3809", size = 4551162, upload-time = "2024-10-11T08:28:27.17Z" }, + { url = "https://files.pythonhosted.org/packages/de/ce/94f2973a61f3937c161842b9f0a32477c7084f4ee1fe2814d6b2678f4e98/pytype-2024.10.11-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dd9ecb48aa46ecef14b39f1bbe8ff7e586e499639a056c05bd4436ca0b35d82", size = 4682398, upload-time = "2024-10-11T08:28:30.029Z" }, + { url = "https://files.pythonhosted.org/packages/26/65/d3b61acd71e96dab8c332bb43f8e7f26251c960b621f86e14a5ffaf35d2f/pytype-2024.10.11-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37d8dfdf23679abfdfe047efef7239a438a038e580d7e0767c0403a6be07cea0", size = 4694193, upload-time = "2024-10-11T08:28:34.239Z" }, + { url = "https://files.pythonhosted.org/packages/23/11/5e31c16d022b724798567e85e313563a78625d7c28247ba0c8ec2741d3e2/pytype-2024.10.11-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:2e31a964aa82e1ac317adbe17b77010e4f362882df1ce7ad15ef0cf0bb97039f", size = 4710919, upload-time = "2024-10-11T08:28:36.984Z" }, + { url = "https://files.pythonhosted.org/packages/ed/03/406255d17ba64acd8c48e8eeba95276084d70dd2e9b92a88bd6ebd013efe/pytype-2024.10.11-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15e2f39590cc08ef8e6704cfa5c1db6fbbee2799891f9d8adbf821f883a54745", size = 4683824, upload-time = "2024-10-11T08:28:39.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a9/7e3776a0ae4fcdb988c013384c92a0e716f1e896c2c0012f446ba932093c/pytype-2024.10.11-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ead3408fc9622ba8a357c9a6b9b49059a9b8add0a3b8390a9ab490f62a984005", size = 4696333, upload-time = "2024-10-11T08:28:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/78/a2/cf47d02d846f6d837e1023b2c19588127c4d9dfc577b8eceb4870def32f4/pytype-2024.10.11-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:cdc881cce9541a475ec48989a5ab889e6274a85afbf6da0e30266d0823f66d42", size = 4709678, upload-time = "2024-10-11T08:28:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/5925fe8992f02adab358ce9e8068defdae1e58393bfd767e035509da9872/pytype-2024.10.11-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13327d0d17b981fe2660dd3a69f97bf09a526f93debc40bb44b240628e0b55c1", size = 4682184, upload-time = "2024-10-11T08:28:32.128Z" }, + { url = "https://files.pythonhosted.org/packages/20/10/52422fdb5a6b4a04938f47a76c655325775b7dd8cc71bd3557aae87b40e1/pytype-2024.10.11-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb98711679e631b01b09b09185504fbf38d60f119280918e244a602cf843b0fe", size = 4695132, upload-time = "2024-10-11T08:28:23.525Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "radon" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "mando" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/6d/98e61600febf6bd929cf04154537c39dc577ce414bafbfc24a286c4fa76d/radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5", size = 1874992, upload-time = "2023-03-26T06:24:38.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859", size = 52784, upload-time = "2023-03-26T06:24:33.949Z" }, +] + +[[package]] +name = "redis" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/dffaaa4374b8d5f3b7ff5c40025c9db387e06264302d5a9da6043cd84e1f/redis-6.0.0.tar.gz", hash = "sha256:5446780d2425b787ed89c91ddbfa1be6d32370a636c8fdb687f11b1c26c1fa88", size = 4620969, upload-time = "2025-04-30T19:09:30.798Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c8/68081c9d3531f7b2a4d663326b96a9dcbc2aef47df3c6b5c38dea90dff02/redis-6.0.0-py3-none-any.whl", hash = "sha256:a2e040aee2cdd947be1fa3a32e35a956cd839cc4c1dbbe4b2cdee5b9623fd27c", size = 268950, upload-time = "2025-04-30T19:09:28.432Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863, upload-time = "2025-03-26T14:56:01.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/cbc43b220c9deb536b07fbd598c97d463bbb7afb788851891252fc920742/rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724", size = 377531, upload-time = "2025-03-26T14:52:41.754Z" }, + { url = "https://files.pythonhosted.org/packages/42/15/cc4b09ef160483e49c3aab3b56f3d375eadf19c87c48718fb0147e86a446/rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b", size = 362273, upload-time = "2025-03-26T14:52:44.341Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a2/67718a188a88dbd5138d959bed6efe1cc7413a4caa8283bd46477ed0d1ad/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727", size = 388111, upload-time = "2025-03-26T14:52:46.944Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e6/cbf1d3163405ad5f4a1a6d23f80245f2204d0c743b18525f34982dec7f4d/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964", size = 394447, upload-time = "2025-03-26T14:52:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/21/bb/4fe220ccc8a549b38b9e9cec66212dc3385a82a5ee9e37b54411cce4c898/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5", size = 448028, upload-time = "2025-03-26T14:52:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/a5/41/d2d6e0fd774818c4cadb94185d30cf3768de1c2a9e0143fc8bc6ce59389e/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664", size = 447410, upload-time = "2025-03-26T14:52:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a7/6d04d438f53d8bb2356bb000bea9cf5c96a9315e405b577117e344cc7404/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc", size = 389531, upload-time = "2025-03-26T14:52:54.233Z" }, + { url = "https://files.pythonhosted.org/packages/23/be/72e6df39bd7ca5a66799762bf54d8e702483fdad246585af96723109d486/rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0", size = 420099, upload-time = "2025-03-26T14:52:56.135Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c9/ca100cd4688ee0aa266197a5cb9f685231676dd7d573041ca53787b23f4e/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f", size = 564950, upload-time = "2025-03-26T14:52:57.583Z" }, + { url = "https://files.pythonhosted.org/packages/05/98/908cd95686d33b3ac8ac2e582d7ae38e2c3aa2c0377bf1f5663bafd1ffb2/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f", size = 591778, upload-time = "2025-03-26T14:52:59.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ac/e143726f1dd3215efcb974b50b03bd08a8a1556b404a0a7872af6d197e57/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875", size = 560421, upload-time = "2025-03-26T14:53:01.422Z" }, + { url = "https://files.pythonhosted.org/packages/60/28/add1c1d2fcd5aa354f7225d036d4492261759a22d449cff14841ef36a514/rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07", size = 222089, upload-time = "2025-03-26T14:53:02.859Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/81f8066c6de44c507caca488ba336ae30d35d57f61fe10578824d1a70196/rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052", size = 234622, upload-time = "2025-03-26T14:53:04.676Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679, upload-time = "2025-03-26T14:53:06.557Z" }, + { url = "https://files.pythonhosted.org/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571, upload-time = "2025-03-26T14:53:08.439Z" }, + { url = "https://files.pythonhosted.org/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012, upload-time = "2025-03-26T14:53:10.314Z" }, + { url = "https://files.pythonhosted.org/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730, upload-time = "2025-03-26T14:53:11.953Z" }, + { url = "https://files.pythonhosted.org/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264, upload-time = "2025-03-26T14:53:13.42Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813, upload-time = "2025-03-26T14:53:15.036Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438, upload-time = "2025-03-26T14:53:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416, upload-time = "2025-03-26T14:53:18.671Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236, upload-time = "2025-03-26T14:53:20.357Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016, upload-time = "2025-03-26T14:53:22.216Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123, upload-time = "2025-03-26T14:53:23.733Z" }, + { url = "https://files.pythonhosted.org/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256, upload-time = "2025-03-26T14:53:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718, upload-time = "2025-03-26T14:53:26.631Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945, upload-time = "2025-03-26T14:53:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935, upload-time = "2025-03-26T14:53:29.684Z" }, + { url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817, upload-time = "2025-03-26T14:53:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983, upload-time = "2025-03-26T14:53:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719, upload-time = "2025-03-26T14:53:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546, upload-time = "2025-03-26T14:53:36.26Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695, upload-time = "2025-03-26T14:53:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218, upload-time = "2025-03-26T14:53:39.326Z" }, + { url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062, upload-time = "2025-03-26T14:53:40.885Z" }, + { url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262, upload-time = "2025-03-26T14:53:42.544Z" }, + { url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306, upload-time = "2025-03-26T14:53:44.2Z" }, + { url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281, upload-time = "2025-03-26T14:53:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719, upload-time = "2025-03-26T14:53:47.187Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072, upload-time = "2025-03-26T14:53:48.686Z" }, + { url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919, upload-time = "2025-03-26T14:53:50.229Z" }, + { url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360, upload-time = "2025-03-26T14:53:51.909Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704, upload-time = "2025-03-26T14:53:53.47Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839, upload-time = "2025-03-26T14:53:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494, upload-time = "2025-03-26T14:53:57.047Z" }, + { url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185, upload-time = "2025-03-26T14:53:59.032Z" }, + { url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168, upload-time = "2025-03-26T14:54:00.661Z" }, + { url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622, upload-time = "2025-03-26T14:54:02.312Z" }, + { url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435, upload-time = "2025-03-26T14:54:04.388Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762, upload-time = "2025-03-26T14:54:06.422Z" }, + { url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510, upload-time = "2025-03-26T14:54:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075, upload-time = "2025-03-26T14:54:09.992Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974, upload-time = "2025-03-26T14:54:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730, upload-time = "2025-03-26T14:54:13.145Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627, upload-time = "2025-03-26T14:54:14.711Z" }, + { url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094, upload-time = "2025-03-26T14:54:16.961Z" }, + { url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639, upload-time = "2025-03-26T14:54:19.047Z" }, + { url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584, upload-time = "2025-03-26T14:54:20.722Z" }, + { url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047, upload-time = "2025-03-26T14:54:22.426Z" }, + { url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085, upload-time = "2025-03-26T14:54:23.949Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498, upload-time = "2025-03-26T14:54:25.573Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202, upload-time = "2025-03-26T14:54:27.569Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771, upload-time = "2025-03-26T14:54:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195, upload-time = "2025-03-26T14:54:31.581Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354, upload-time = "2025-03-26T14:54:33.199Z" }, + { url = "https://files.pythonhosted.org/packages/99/48/11dae46d0c7f7e156ca0971a83f89c510af0316cd5d42c771b7cef945f0c/rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a", size = 378224, upload-time = "2025-03-26T14:54:58.78Z" }, + { url = "https://files.pythonhosted.org/packages/33/18/e8398d255369e35d312942f3bb8ecaff013c44968904891be2ab63b3aa94/rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399", size = 363252, upload-time = "2025-03-26T14:55:00.359Z" }, + { url = "https://files.pythonhosted.org/packages/17/39/dd73ba691f4df3e6834bf982de214086ac3359ab3ac035adfb30041570e3/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098", size = 388871, upload-time = "2025-03-26T14:55:02.253Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/da0530b25cabd0feca2a759b899d2df325069a94281eeea8ac44c6cfeff7/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d", size = 394766, upload-time = "2025-03-26T14:55:04.05Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ee/dd1c5040a431beb40fad4a5d7868acf343444b0bc43e627c71df2506538b/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e", size = 448712, upload-time = "2025-03-26T14:55:06.03Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ec/6b93ffbb686be948e4d91ec76f4e6757f8551034b2a8176dd848103a1e34/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1", size = 447150, upload-time = "2025-03-26T14:55:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/55/d5/a1c23760adad85b432df074ced6f910dd28f222b8c60aeace5aeb9a6654e/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb", size = 390662, upload-time = "2025-03-26T14:55:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f3/419cb1f9bfbd3a48c256528c156e00f3349e3edce5ad50cbc141e71f66a5/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44", size = 421351, upload-time = "2025-03-26T14:55:11.477Z" }, + { url = "https://files.pythonhosted.org/packages/98/8e/62d1a55078e5ede0b3b09f35e751fa35924a34a0d44d7c760743383cd54a/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33", size = 566074, upload-time = "2025-03-26T14:55:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/fc/69/b7d1003166d78685da032b3c4ff1599fa536a3cfe6e5ce2da87c9c431906/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164", size = 592398, upload-time = "2025-03-26T14:55:15.202Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a8/1c98bc99338c37faadd28dd667d336df7409d77b4da999506a0b6b1c0aa2/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc", size = 561114, upload-time = "2025-03-26T14:55:17.072Z" }, + { url = "https://files.pythonhosted.org/packages/2b/41/65c91443685a4c7b5f1dd271beadc4a3e063d57c3269221548dd9416e15c/rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5", size = 235548, upload-time = "2025-03-26T14:55:18.707Z" }, + { url = "https://files.pythonhosted.org/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386, upload-time = "2025-03-26T14:55:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440, upload-time = "2025-03-26T14:55:22.121Z" }, + { url = "https://files.pythonhosted.org/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816, upload-time = "2025-03-26T14:55:23.737Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058, upload-time = "2025-03-26T14:55:25.468Z" }, + { url = "https://files.pythonhosted.org/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692, upload-time = "2025-03-26T14:55:27.535Z" }, + { url = "https://files.pythonhosted.org/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462, upload-time = "2025-03-26T14:55:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460, upload-time = "2025-03-26T14:55:31.017Z" }, + { url = "https://files.pythonhosted.org/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609, upload-time = "2025-03-26T14:55:32.84Z" }, + { url = "https://files.pythonhosted.org/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818, upload-time = "2025-03-26T14:55:34.538Z" }, + { url = "https://files.pythonhosted.org/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627, upload-time = "2025-03-26T14:55:36.26Z" }, + { url = "https://files.pythonhosted.org/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885, upload-time = "2025-03-26T14:55:38Z" }, +] + +[[package]] +name = "ruff" +version = "0.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, +] + +[[package]] +name = "settings-doc" +version = "4.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c8/ac0ebe94fc41e7c03a5be9f6aab1612e79a46bfad286a76fb7cd41a8cd50/settings_doc-4.3.2.tar.gz", hash = "sha256:cb06aee969f0639abc88e77554a333803191de95e95259a11929cf878d312fab", size = 16274, upload-time = "2025-01-02T19:37:27.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/48/86c853f6f98a0340594c751930ab876b09b28d4c29a0b218923eb95046c8/settings_doc-4.3.2-py3-none-any.whl", hash = "sha256:04b561093905cab8f5ebaa30c9dacca1d57cd1dc3dd404b7c929b90e2d2d7c0b", size = 14461, upload-time = "2025-01-02T19:37:23.641Z" }, +] + +[[package]] +name = "setuptools" +version = "80.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008, upload-time = "2025-05-09T20:42:27.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812, upload-time = "2025-05-09T20:42:25.325Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snakeviz" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/06/82f56563b16d33c2586ac2615a3034a83a4ff1969b84c8d79339e5d07d73/snakeviz-2.2.2.tar.gz", hash = "sha256:08028c6f8e34a032ff14757a38424770abb8662fb2818985aeea0d9bc13a7d83", size = 182039, upload-time = "2024-11-09T22:03:58.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/f7/83b00cdf4f114f10750a18b64c27dc34636d0ac990ccac98282f5c0fbb43/snakeviz-2.2.2-py3-none-any.whl", hash = "sha256:77e7b9c82f6152edc330040319b97612351cd9b48c706434c535c2df31d10ac5", size = 183477, upload-time = "2024-11-09T22:03:57.049Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.40" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299, upload-time = "2025-03-27T17:52:31.876Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fa/8e8fd93684b04e65816be864bebf0000fe1602e5452d006f9acc5db14ce5/sqlalchemy-2.0.40-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1ea21bef99c703f44444ad29c2c1b6bd55d202750b6de8e06a955380f4725d7", size = 2112843, upload-time = "2025-03-27T18:49:25.515Z" }, + { url = "https://files.pythonhosted.org/packages/ba/87/06992f78a9ce545dfd1fea3dd99262bec5221f6f9d2d2066c3e94662529f/sqlalchemy-2.0.40-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:afe63b208153f3a7a2d1a5b9df452b0673082588933e54e7c8aac457cf35e758", size = 2104032, upload-time = "2025-03-27T18:49:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/92/ee/57dc77282e8be22d686bd4681825299aa1069bbe090564868ea270ed5214/sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8aae085ea549a1eddbc9298b113cffb75e514eadbb542133dd2b99b5fb3b6af", size = 3086406, upload-time = "2025-03-27T18:44:25.302Z" }, + { url = "https://files.pythonhosted.org/packages/94/3f/ceb9ab214b2e42d2e74a9209b3a2f2f073504eee16cddd2df81feeb67c2f/sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ea9181284754d37db15156eb7be09c86e16e50fbe77610e9e7bee09291771a1", size = 3094652, upload-time = "2025-03-27T18:55:16.174Z" }, + { url = "https://files.pythonhosted.org/packages/00/0a/3401232a5b6d91a2df16c1dc39c6504c54575744c2faafa1e5a50de96621/sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5434223b795be5c5ef8244e5ac98056e290d3a99bdcc539b916e282b160dda00", size = 3050503, upload-time = "2025-03-27T18:44:28.266Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/ea7171415ab131397f71a2673645c2fe29ebe9a93063d458eb89e42bf051/sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15d08d5ef1b779af6a0909b97be6c1fd4298057504eb6461be88bd1696cb438e", size = 3076011, upload-time = "2025-03-27T18:55:17.967Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ee/d8e229280d621bed8c51eebf1dd413aa09ca89e309b1fff40d881dd149af/sqlalchemy-2.0.40-cp310-cp310-win32.whl", hash = "sha256:cd2f75598ae70bcfca9117d9e51a3b06fe29edd972fdd7fd57cc97b4dbf3b08a", size = 2085136, upload-time = "2025-03-27T18:48:53.032Z" }, + { url = "https://files.pythonhosted.org/packages/60/7f/ea1086136bc648cd4713a1e01869f7fc31979d67b3a8f973f5d9ab8de7e1/sqlalchemy-2.0.40-cp310-cp310-win_amd64.whl", hash = "sha256:2cbafc8d39ff1abdfdda96435f38fab141892dc759a2165947d1a8fffa7ef596", size = 2109421, upload-time = "2025-03-27T18:48:54.258Z" }, + { url = "https://files.pythonhosted.org/packages/77/7e/55044a9ec48c3249bb38d5faae93f09579c35e862bb318ebd1ed7a1994a5/sqlalchemy-2.0.40-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6bacab7514de6146a1976bc56e1545bee247242fab030b89e5f70336fc0003e", size = 2114025, upload-time = "2025-03-27T18:49:29.456Z" }, + { url = "https://files.pythonhosted.org/packages/77/0f/dcf7bba95f847aec72f638750747b12d37914f71c8cc7c133cf326ab945c/sqlalchemy-2.0.40-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5654d1ac34e922b6c5711631f2da497d3a7bffd6f9f87ac23b35feea56098011", size = 2104419, upload-time = "2025-03-27T18:49:30.75Z" }, + { url = "https://files.pythonhosted.org/packages/75/70/c86a5c20715e4fe903dde4c2fd44fc7e7a0d5fb52c1b954d98526f65a3ea/sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35904d63412db21088739510216e9349e335f142ce4a04b69e2528020ee19ed4", size = 3222720, upload-time = "2025-03-27T18:44:29.871Z" }, + { url = "https://files.pythonhosted.org/packages/12/cf/b891a8c1d0c27ce9163361664c2128c7a57de3f35000ea5202eb3a2917b7/sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7a80ed86d6aaacb8160a1caef6680d4ddd03c944d985aecee940d168c411d1", size = 3222682, upload-time = "2025-03-27T18:55:20.097Z" }, + { url = "https://files.pythonhosted.org/packages/15/3f/7709d8c8266953d945435a96b7f425ae4172a336963756b58e996fbef7f3/sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:519624685a51525ddaa7d8ba8265a1540442a2ec71476f0e75241eb8263d6f51", size = 3159542, upload-time = "2025-03-27T18:44:31.333Z" }, + { url = "https://files.pythonhosted.org/packages/85/7e/717eaabaf0f80a0132dc2032ea8f745b7a0914451c984821a7c8737fb75a/sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ee5f9999a5b0e9689bed96e60ee53c3384f1a05c2dd8068cc2e8361b0df5b7a", size = 3179864, upload-time = "2025-03-27T18:55:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cc/03eb5dfcdb575cbecd2bd82487b9848f250a4b6ecfb4707e834b4ce4ec07/sqlalchemy-2.0.40-cp311-cp311-win32.whl", hash = "sha256:c0cae71e20e3c02c52f6b9e9722bca70e4a90a466d59477822739dc31ac18b4b", size = 2084675, upload-time = "2025-03-27T18:48:55.915Z" }, + { url = "https://files.pythonhosted.org/packages/9a/48/440946bf9dc4dc231f4f31ef0d316f7135bf41d4b86aaba0c0655150d370/sqlalchemy-2.0.40-cp311-cp311-win_amd64.whl", hash = "sha256:574aea2c54d8f1dd1699449f332c7d9b71c339e04ae50163a3eb5ce4c4325ee4", size = 2110099, upload-time = "2025-03-27T18:48:57.45Z" }, + { url = "https://files.pythonhosted.org/packages/92/06/552c1f92e880b57d8b92ce6619bd569b25cead492389b1d84904b55989d8/sqlalchemy-2.0.40-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d3b31d0a1c44b74d3ae27a3de422dfccd2b8f0b75e51ecb2faa2bf65ab1ba0d", size = 2112620, upload-time = "2025-03-27T18:40:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/a5bc6e76c34cebc071f758161dbe1453de8815ae6e662393910d3be6d70d/sqlalchemy-2.0.40-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f7a0f506cf78c80450ed1e816978643d3969f99c4ac6b01104a6fe95c5490a", size = 2103004, upload-time = "2025-03-27T18:40:04.204Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fd/0e96c8e6767618ed1a06e4d7a167fe13734c2f8113c4cb704443e6783038/sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bb933a650323e476a2e4fbef8997a10d0003d4da996aad3fd7873e962fdde4d", size = 3252440, upload-time = "2025-03-27T18:51:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/cd/6a/eb82e45b15a64266a2917a6833b51a334ea3c1991728fd905bfccbf5cf63/sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6959738971b4745eea16f818a2cd086fb35081383b078272c35ece2b07012716", size = 3263277, upload-time = "2025-03-27T18:50:28.142Z" }, + { url = "https://files.pythonhosted.org/packages/45/97/ebe41ab4530f50af99e3995ebd4e0204bf1b0dc0930f32250dde19c389fe/sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:110179728e442dae85dd39591beb74072ae4ad55a44eda2acc6ec98ead80d5f2", size = 3198591, upload-time = "2025-03-27T18:51:27.543Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1c/a569c1b2b2f5ac20ba6846a1321a2bf52e9a4061001f282bf1c5528dcd69/sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8040680eaacdce4d635f12c55c714f3d4c7f57da2bc47a01229d115bd319191", size = 3225199, upload-time = "2025-03-27T18:50:30.069Z" }, + { url = "https://files.pythonhosted.org/packages/8f/91/87cc71a6b10065ca0209d19a4bb575378abda6085e72fa0b61ffb2201b84/sqlalchemy-2.0.40-cp312-cp312-win32.whl", hash = "sha256:650490653b110905c10adac69408380688cefc1f536a137d0d69aca1069dc1d1", size = 2082959, upload-time = "2025-03-27T18:45:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/14c511cda174aa1ad9b0e42b64ff5a71db35d08b0d80dc044dae958921e5/sqlalchemy-2.0.40-cp312-cp312-win_amd64.whl", hash = "sha256:2be94d75ee06548d2fc591a3513422b873490efb124048f50556369a834853b0", size = 2108526, upload-time = "2025-03-27T18:45:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887, upload-time = "2025-03-27T18:40:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367, upload-time = "2025-03-27T18:40:07.182Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806, upload-time = "2025-03-27T18:51:29.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131, upload-time = "2025-03-27T18:50:31.616Z" }, + { url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364, upload-time = "2025-03-27T18:51:31.336Z" }, + { url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482, upload-time = "2025-03-27T18:50:33.201Z" }, + { url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704, upload-time = "2025-03-27T18:46:00.193Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564, upload-time = "2025-03-27T18:46:01.442Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894, upload-time = "2025-03-27T18:40:43.796Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/be/7e776a29b5f712b5bd13c571256a2470fcf345c562c7b2359f2ee15d9355/sse_starlette-2.3.4.tar.gz", hash = "sha256:0ffd6bed217cdbb74a84816437c609278003998b4991cd2e6872d0b35130e4d5", size = 17522, upload-time = "2025-05-04T19:28:51.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/a4/ee4a20f0b5ff34c391f3685eff7cdba1178a487766e31b04efb51bbddd87/sse_starlette-2.3.4-py3-none-any.whl", hash = "sha256:b8100694f3f892b133d0f7483acb7aacfcf6ed60f863b31947664b6dc74e529f", size = 10232, upload-time = "2025-05-04T19:28:50.199Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "stevedore" +version = "5.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pbr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/3f/13cacea96900bbd31bb05c6b74135f85d15564fc583802be56976c940470/stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b", size = 513858, upload-time = "2025-02-20T14:03:57.285Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/45/8c4ebc0c460e6ec38e62ab245ad3c7fc10b210116cea7c16d61602aa9558/stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe", size = 49533, upload-time = "2025-02-20T14:03:55.849Z" }, +] + +[[package]] +name = "tabledata" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dataproperty" }, + { name = "typepy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/35/171c8977162f1163368406deddde4c59673b62bd0cb2f34948a02effb075/tabledata-1.3.4.tar.gz", hash = "sha256:e9649cab129d718f3bff4150083b77f8a78c30f6634a30caf692b10fdc60cb97", size = 25074, upload-time = "2024-12-31T14:12:31.198Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/64/fa4160151976ee4b2cf0c1217a99443ffaeb991956feddfeac9eee9952f8/tabledata-1.3.4-py3-none-any.whl", hash = "sha256:1f56e433bfdeb89f4487abfa48c4603a3b07c5d3a3c7e05ff73dd018c24bd0d4", size = 11820, upload-time = "2024-12-31T14:12:28.584Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tcolorpy" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/cc/44f2d81d8f9093aad81c3467a5bf5718d2b5f786e887b6e4adcfc17ec6b9/tcolorpy-0.1.7.tar.gz", hash = "sha256:0fbf6bf238890bbc2e32662aa25736769a29bf6d880328f310c910a327632614", size = 299437, upload-time = "2024-12-29T15:24:23.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/a2/ed023f2edd1e011b4d99b6727bce8253842d66c3fbf9ed0a26fc09a92571/tcolorpy-0.1.7-py3-none-any.whl", hash = "sha256:26a59d52027e175a37e0aba72efc99dda43f074db71f55b316d3de37d3251378", size = 8096, upload-time = "2024-12-29T15:24:21.33Z" }, +] + +[[package]] +name = "testslide" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "psutil" }, + { name = "pygments" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/c8d6d60a597c693559dab3b3362bd01e2212530e9a163eb0164af81e1ec1/TestSlide-2.7.1.tar.gz", hash = "sha256:d25890d5c383f673fac44a5f9e2561b7118d04f29f2c2b3d4f549e6db94cb34d", size = 50255, upload-time = "2023-03-16T14:09:41.204Z" } + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload-time = "2024-08-14T08:19:41.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload-time = "2024-08-14T08:19:40.05Z" }, +] + +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135, upload-time = "2024-11-22T03:06:38.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299, upload-time = "2024-11-22T03:06:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253, upload-time = "2024-11-22T03:06:22.39Z" }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602, upload-time = "2024-11-22T03:06:24.214Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972, upload-time = "2024-11-22T03:06:25.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173, upload-time = "2024-11-22T03:06:27.584Z" }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892, upload-time = "2024-11-22T03:06:28.933Z" }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334, upload-time = "2024-11-22T03:06:30.428Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261, upload-time = "2024-11-22T03:06:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463, upload-time = "2024-11-22T03:06:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907, upload-time = "2024-11-22T03:06:36.71Z" }, +] + +[[package]] +name = "trove-classifiers" +version = "2025.5.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/04/1cd43f72c241fedcf0d9a18d0783953ee301eac9e5d9db1df0f0f089d9af/trove_classifiers-2025.5.9.12.tar.gz", hash = "sha256:7ca7c8a7a76e2cd314468c677c69d12cc2357711fcab4a60f87994c1589e5cb5", size = 16940, upload-time = "2025-05-09T12:04:48.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/ef/c6deb083748be3bcad6f471b6ae983950c161890bf5ae1b2af80cc56c530/trove_classifiers-2025.5.9.12-py3-none-any.whl", hash = "sha256:e381c05537adac78881c8fa345fd0e9970159f4e4a04fcc42cfd3129cca640ce", size = 14119, upload-time = "2025-05-09T12:04:46.38Z" }, +] + +[[package]] +name = "typed-ast" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/7e/a424029f350aa8078b75fd0d360a787a273ca753a678d1104c5fa4f3072a/typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd", size = 252841, upload-time = "2023-07-04T18:38:08.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/07/5defe18d4fc16281cd18c4374270abc430c3d852d8ac29b5db6599d45cfe/typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b", size = 223267, upload-time = "2023-07-04T18:37:00.129Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5c/e379b00028680bfcd267d845cf46b60e76d8ac6f7009fd440d6ce030cc92/typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686", size = 208260, upload-time = "2023-07-04T18:37:03.069Z" }, + { url = "https://files.pythonhosted.org/packages/3b/99/5cc31ef4f3c80e1ceb03ed2690c7085571e3fbf119cbd67a111ec0b6622f/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769", size = 842272, upload-time = "2023-07-04T18:37:04.916Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ed/b9b8b794b37b55c9247b1e8d38b0361e8158795c181636d34d6c11b506e7/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04", size = 824651, upload-time = "2023-07-04T18:37:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/ca/59/dbbbe5a0e91c15d14a0896b539a5ed01326b0d468e75c1a33274d128d2d1/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d", size = 854960, upload-time = "2023-07-04T18:37:08.474Z" }, + { url = "https://files.pythonhosted.org/packages/90/f0/0956d925f87bd81f6e0f8cf119eac5e5c8f4da50ca25bb9f5904148d4611/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d", size = 839321, upload-time = "2023-07-04T18:37:10.417Z" }, + { url = "https://files.pythonhosted.org/packages/43/17/4bdece9795da6f3345c4da5667ac64bc25863617f19c28d81f350f515be6/typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02", size = 139380, upload-time = "2023-07-04T18:37:12.157Z" }, + { url = "https://files.pythonhosted.org/packages/75/53/b685e10da535c7b3572735f8bea0d4abb35a04722a7d44ca9c163a0cf822/typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee", size = 223264, upload-time = "2023-07-04T18:37:13.637Z" }, + { url = "https://files.pythonhosted.org/packages/96/fd/fc8ccf19fc16a40a23e7c7802d0abc78c1f38f1abb6e2447c474f8a076d8/typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18", size = 208158, upload-time = "2023-07-04T18:37:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/598e47f2c3ecd19d7f1bb66854d0d3ba23ffd93c846448790a92524b0a8d/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88", size = 878366, upload-time = "2023-07-04T18:37:16.614Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/765e8bf8b24d0ed7b9fc669f6826c5bc3eb7412fc765691f59b83ae195b2/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2", size = 860314, upload-time = "2023-07-04T18:37:18.215Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3c/4af750e6c673a0dd6c7b9f5b5e5ed58ec51a2e4e744081781c664d369dfa/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9", size = 898108, upload-time = "2023-07-04T18:37:20.095Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/d0a4d1e060e1e8dda2408131a0cc7633fc4bc99fca5941dcb86c461dfe01/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8", size = 881971, upload-time = "2023-07-04T18:37:21.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/f28d2c912cd010a09b3677ac69d23181045eb17e358914ab739b7fdee530/typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b", size = 139286, upload-time = "2023-07-04T18:37:23.625Z" }, +] + +[[package]] +name = "typeguard" +version = "2.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/38/c61bfcf62a7b572b5e9363a802ff92559cb427ee963048e1442e3aef7490/typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", size = 40604, upload-time = "2021-12-10T21:09:39.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605, upload-time = "2021-12-10T21:09:37.844Z" }, +] + +[[package]] +name = "typepy" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mbstrdecoder" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/59/4c39942077d7de285f762a91024dbda731be693591732977358f77d120fb/typepy-1.3.4.tar.gz", hash = "sha256:89c1f66de6c6133209c43a94d23431d320ba03ef5db18f241091ea594035d9de", size = 39558, upload-time = "2024-12-29T09:18:15.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/31/e393c3830bdedd01735bd195c85ac3034b6bcaf6c18142bab60a4047ca36/typepy-1.3.4-py3-none-any.whl", hash = "sha256:d5ed3e0c7f49521bff0603dd08cf8d453371cf68d65a29d3d0038552ccc46e2e", size = 31449, upload-time = "2024-12-29T09:18:13.135Z" }, +] + +[package.optional-dependencies] +datetime = [ + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "pytz" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241206" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802, upload-time = "2024-12-06T02:56:41.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384, upload-time = "2024-12-06T02:56:39.412Z" }, +] + +[[package]] +name = "types-tabulate" +version = "0.9.0.20241207" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195, upload-time = "2024-12-07T02:54:42.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307, upload-time = "2024-12-07T02:54:41.031Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, +] + +[[package]] +name = "wcmatch" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/ab/b3a52228538ccb983653c446c1656eddf1d5303b9cb8b9aef6a91299f862/wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a", size = 115578, upload-time = "2024-09-26T18:39:52.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/df/4ee467ab39cc1de4b852c212c1ed3becfec2e486a51ac1ce0091f85f38d7/wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a", size = 39347, upload-time = "2024-09-26T18:39:51.002Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" }, +]

(ox~6i@S1Pxn+u$23j0Y}fW<#};klR&M8( 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