From b6c13a814c6b4f7a710ec3a70ac182b46c1378f7 Mon Sep 17 00:00:00 2001 From: Jonathan Doughty Date: Tue, 16 Jun 2026 15:23:08 -0400 Subject: [PATCH] refactor: overhaul health check system architecture including collectors, remediation, and reporting modules --- .github/workflows/ci.yml | 3 + .github/workflows/release.yml | 6 +- CONTRIBUTING.md | 6 +- README.md | 2 +- elastro-logloom.ndjson | 125 +++++ elastro/cli/commands/health.py | 56 +- elastro/cli/commands/index.py | 18 +- elastro/cli/commands/snapshot.py | 9 +- elastro/cli/deletion.py | 15 +- elastro/core/errors.py | 4 + elastro/core/ilm.py | 4 +- elastro/core/logger.py | 6 +- elastro/health/__init__.py | 7 +- elastro/health/assessor.py | 13 +- elastro/health/audit.py | 2 +- elastro/health/collectors/__init__.py | 2 +- elastro/health/collectors/base.py | 2 +- elastro/health/collectors/cluster.py | 2 +- elastro/health/collectors/disk.py | 11 +- elastro/health/collectors/health_report.py | 6 +- elastro/health/collectors/ilm.py | 10 +- elastro/health/collectors/mappings.py | 2 +- elastro/health/collectors/nodes.py | 2 +- elastro/health/collectors/security.py | 14 +- elastro/health/collectors/shards.py | 2 +- elastro/health/collectors/snapshots.py | 2 +- elastro/health/config.py | 2 +- elastro/health/disk_blocks.py | 2 +- elastro/health/exit_policy.py | 2 +- elastro/health/finding_guides/__init__.py | 2 +- elastro/health/finding_guides/oversharding.py | 16 +- elastro/health/formatters/__init__.py | 2 +- elastro/health/formatters/history_table.py | 8 +- elastro/health/formatters/hotspots_table.py | 2 +- elastro/health/formatters/ilm_table.py | 2 +- elastro/health/formatters/json_fmt.py | 2 +- elastro/health/formatters/lint_table.py | 14 +- elastro/health/formatters/nodes_table.py | 2 +- elastro/health/formatters/render.py | 2 +- elastro/health/formatters/shards_table.py | 2 +- elastro/health/formatters/table.py | 2 +- elastro/health/formatters/yaml_fmt.py | 2 +- elastro/health/history.py | 12 +- elastro/health/ilm_status.py | 18 +- elastro/health/lint.py | 20 +- elastro/health/manager.py | 6 +- elastro/health/mappings.py | 2 +- elastro/health/models.py | 6 +- elastro/health/remediation/__init__.py | 2 +- .../health/remediation/actions/__init__.py | 2 +- .../remediation/actions/clear_read_only.py | 6 +- .../actions/clear_routing_filters.py | 7 +- .../health/remediation/actions/ilm_retry.py | 2 +- .../remediation/actions/reduce_replicas.py | 11 +- .../remediation/actions/reroute_failed.py | 2 +- elastro/health/remediation/catalog.py | 2 +- elastro/health/remediation/diagnosis.py | 11 +- elastro/health/remediation/display.py | 21 +- elastro/health/remediation/dry_run.py | 6 +- elastro/health/remediation/executor.py | 2 +- elastro/health/remediation/fix.py | 13 +- elastro/health/remediation/models.py | 2 - elastro/health/remediation/planner.py | 2 +- elastro/health/remediation/rollback.py | 6 +- elastro/health/remediation/safety.py | 12 +- elastro/health/rules/__init__.py | 2 +- elastro/health/rules/backup_policy.py | 6 +- elastro/health/rules/circuit_breaker.py | 2 +- elastro/health/rules/cluster_blocks.py | 6 +- elastro/health/rules/cluster_capacity.py | 10 +- elastro/health/rules/cpu_pressure.py | 2 +- elastro/health/rules/engine.py | 2 +- elastro/health/rules/hotspots.py | 2 +- elastro/health/rules/jvm.py | 2 +- elastro/health/rules/mapping_explosion.py | 10 +- elastro/health/rules/master_topology.py | 2 +- elastro/health/rules/oversharding.py | 16 +- elastro/health/rules/persistent_yellow.py | 9 +- elastro/health/rules/replica.py | 2 +- elastro/health/rules/shards_unassigned.py | 2 +- elastro/health/rules/thread_pool.py | 2 +- elastro/health/scoring.py | 2 +- elastro/health/shards.py | 6 +- elastro/health/trends.py | 6 +- elastro/health/version.py | 2 +- elastro/server/__init__.py | 4 +- elastro/server/cluster_inventory.py | 20 +- elastro/server/health_cache.py | 2 +- elastro/server/routes/health.py | 10 +- elastro/utils/health.py | 2 +- examples/datastreams.py | 2 +- pyproject.toml | 22 +- .../ingest_stress/ingest_evaluator.py | 116 +++-- .../ingest_stress/stress_tester.py | 10 +- tests/integration/test_health_api.py | 2 +- tests/integration/test_health_assess.py | 2 +- tests/integration/test_health_exit_codes.py | 17 +- tests/integration/test_health_fix.py | 28 +- tests/integration/test_health_ilm.py | 2 +- tests/integration/test_health_lint.py | 2 +- tests/integration/test_health_nodes.py | 6 +- tests/integration/test_health_rollback.py | 2 +- tests/integration/test_health_shards.py | 12 +- tests/integration/test_health_trends.py | 2 +- tests/integration/test_index_fix.py | 10 +- tests/unit/cli/test_deletion_dry_run.py | 13 +- tests/unit/core/test_client.py | 1 - tests/unit/core/test_compliance_patterns.py | 8 +- tests/unit/core/test_ingest_readers.py | 2 +- tests/unit/core/test_ingest_sql.py | 4 +- tests/unit/core/test_logger.py | 2 +- tests/unit/health/test_assessor.py | 12 +- tests/unit/health/test_audit.py | 2 +- tests/unit/health/test_collectors.py | 2 +- tests/unit/health/test_common_issue_rules.py | 2 +- tests/unit/health/test_disk_blocks.py | 2 +- tests/unit/health/test_disk_collector.py | 10 +- tests/unit/health/test_dry_run.py | 9 +- tests/unit/health/test_exit_policy.py | 2 +- tests/unit/health/test_formatters.py | 2 +- tests/unit/health/test_health_report.py | 6 +- tests/unit/health/test_history.py | 6 +- tests/unit/health/test_hotspots_rule.py | 2 +- tests/unit/health/test_ilm_collector.py | 8 +- tests/unit/health/test_ilm_status.py | 2 +- tests/unit/health/test_jvm_rule.py | 2 +- tests/unit/health/test_lint.py | 2 +- .../health/test_mapping_explosion_rule.py | 2 +- tests/unit/health/test_mappings.py | 6 +- tests/unit/health/test_models.py | 2 +- tests/unit/health/test_nodes_collector.py | 2 +- tests/unit/health/test_oversharding_guide.py | 2 +- tests/unit/health/test_oversharding_rule.py | 2 +- .../health/test_persistent_yellow_rule.py | 2 +- .../test_remediation_actions_ilm_disk.py | 2 +- .../unit/health/test_remediation_diagnosis.py | 2 +- .../unit/health/test_remediation_executor.py | 10 +- tests/unit/health/test_remediation_planner.py | 2 +- tests/unit/health/test_remediation_safety.py | 2 +- tests/unit/health/test_replica_rule.py | 2 +- tests/unit/health/test_rollback.py | 2 +- tests/unit/health/test_rule_engine.py | 5 +- tests/unit/health/test_scoring.py | 2 +- tests/unit/health/test_security_collector.py | 2 +- tests/unit/health/test_shards_analysis.py | 2 +- tests/unit/health/test_shards_collector.py | 2 +- tests/unit/health/test_snapshots_collector.py | 2 +- tests/unit/health/test_trends.py | 6 +- tests/unit/server/test_cluster_inventory.py | 3 +- tests/unit/server/test_gui_launch.py | 20 +- uv.lock | 480 +++++++++--------- 151 files changed, 968 insertions(+), 631 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc31119..c13bcd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,9 @@ jobs: python -m pip install --upgrade pip pip install -e ".[dev]" + - name: Code quality - Ruff formatter + run: ruff format --check . + - name: Run health test suite run: | pytest tests/unit/health/ tests/integration/test_health_*.py -q diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95f424e..547cfb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Install build dependencies run: | python -m pip install --upgrade pip - pip install build twine black mypy pip-audit types-requests types-PyYAML types-setuptools + pip install build twine ruff mypy pip-audit types-requests types-PyYAML types-setuptools - name: Set up Node.js uses: actions/setup-node@v4 @@ -40,8 +40,8 @@ jobs: npm ci npm run build - - name: Code Quality - Black Formatter - run: black --check . + - name: Code Quality - Ruff Formatter + run: ruff format --check . - name: Code Quality - Type Safety (Mypy) run: mypy . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index af83030..af654b0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,10 +21,10 @@ pip install -e ".[dev]" We enforce strict quality gates. All checks must pass before merging. ### Formatting & Linting -We use **Black** (formatter) and **Isort** (import sorter). +We use **Ruff** for formatting and linting. ```bash -black . -isort . +ruff format . +ruff check --fix . ``` ### Static Type Checking diff --git a/README.md b/README.md index 78b90a8..fea42e0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) -[![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![Code style: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) A comprehensive Python module for managing Elasticsearch operations within pipeline processes. diff --git a/elastro-logloom.ndjson b/elastro-logloom.ndjson index 4d986ea..55a2ca6 100644 --- a/elastro-logloom.ndjson +++ b/elastro-logloom.ndjson @@ -646,3 +646,128 @@ {"timestamp": "2026-06-16T15:13:23", "level": "ERROR", "logger": "elastro.server.routes.indices", "module": "indices", "function": "get_unhealthy_indices", "line": 103, "message": "Failed to explain allocation for index=logs-000001: explain failed", "logloom_node_id": "ll:04bd9ca1a046", "logloom_module": "server.routes.indices", "logloom_function": "get_unhealthy_indices", "logloom_tags": ["error", "http"], "logloom_file": "elastro/server/routes/indices.py", "logloom_line": 99} {"timestamp": "2026-06-16T15:13:25", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} {"timestamp": "2026-06-16T15:13:25", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:20:20", "level": "ERROR", "logger": "elastro.health.manager", "module": "manager", "function": "cluster_state", "line": 154, "message": "Cluster state request failed: Mock.keys() returned a non-iterable (type Mock)"} +{"timestamp": "2026-06-16T15:20:20", "level": "ERROR", "logger": "elastro.health.collectors.health_report", "module": "health_report", "function": "collect", "line": 163, "message": "Health report collector failed: connection refused"} +{"timestamp": "2026-06-16T15:20:20", "level": "ERROR", "logger": "elastro.health.collectors.nodes", "module": "nodes", "function": "collect", "line": 49, "message": "Nodes collector failed: nodes down"} +{"timestamp": "2026-06-16T15:20:20", "level": "ERROR", "logger": "elastro.health.collectors.snapshots", "module": "snapshots", "function": "collect", "line": 78, "message": "Snapshots collector failed: forbidden"} +{"timestamp": "2026-06-16T15:20:21", "level": "ERROR", "logger": "elastro.server.routes.indices", "module": "indices", "function": "get_unhealthy_indices", "line": 103, "message": "Failed to explain allocation for index=logs-000001: explain failed", "logloom_node_id": "ll:04bd9ca1a046", "logloom_module": "server.routes.indices", "logloom_function": "get_unhealthy_indices", "logloom_tags": ["error", "http"], "logloom_file": "elastro/server/routes/indices.py", "logloom_line": 99} +{"timestamp": "2026-06-16T15:20:22", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:20:22", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:54", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:56", "level": "ERROR", "logger": "elastro.server.routes.indices", "module": "indices", "function": "get_unhealthy_indices", "line": 103, "message": "Failed to explain allocation for index=logs-000001: explain failed", "logloom_node_id": "ll:04bd9ca1a046", "logloom_module": "server.routes.indices", "logloom_function": "get_unhealthy_indices", "logloom_tags": ["error", "http"], "logloom_file": "elastro/server/routes/indices.py", "logloom_line": 99} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "allocation_explain", "line": 336, "message": "Failed to explain allocation for index 'locked-index': Client is not connected. Call connect() first.", "logloom_node_id": "ll:116d2fc542fb", "logloom_module": "elastro.core.index", "logloom_function": "allocation_explain", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 336} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 222, "message": "Connection failed: Connection error", "logloom_node_id": "ll:4aa4882295bb", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 222} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 326, "message": "Lost connection during health check", "logloom_node_id": "ll:e075c1163938", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 326} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 331, "message": "Transport error during health check: 500", "logloom_node_id": "ll:40accf9d9745", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 331} +{"timestamp": "2026-06-16T15:21:57", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 334, "message": "Unexpected error during health check", "logloom_node_id": "ll:7fca919ec8da", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 334} +{"timestamp": "2026-06-16T15:21:58", "level": "ERROR", "logger": "elastro.core.ingest.engine", "module": "engine", "function": "ingest", "line": 191, "message": "Aborting: max error threshold (5) reached", "logloom_node_id": "ll:89b9a61ebf74", "logloom_module": "core.ingest.engine", "logloom_function": "ingest", "logloom_tags": ["error"], "logloom_file": "elastro/core/ingest/engine.py", "logloom_line": 191} +{"timestamp": "2026-06-16T15:22:02", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:02", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:03", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:04", "level": "ERROR", "logger": "elastro.server.routes.indices", "module": "indices", "function": "get_unhealthy_indices", "line": 103, "message": "Failed to explain allocation for index=logs-000001: explain failed", "logloom_node_id": "ll:04bd9ca1a046", "logloom_module": "server.routes.indices", "logloom_function": "get_unhealthy_indices", "logloom_tags": ["error", "http"], "logloom_file": "elastro/server/routes/indices.py", "logloom_line": 99} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "allocation_explain", "line": 336, "message": "Failed to explain allocation for index 'locked-index': Client is not connected. Call connect() first.", "logloom_node_id": "ll:116d2fc542fb", "logloom_module": "elastro.core.index", "logloom_function": "allocation_explain", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 336} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 222, "message": "Connection failed: Connection error", "logloom_node_id": "ll:4aa4882295bb", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 222} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 326, "message": "Lost connection during health check", "logloom_node_id": "ll:e075c1163938", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 326} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 331, "message": "Transport error during health check: 500", "logloom_node_id": "ll:40accf9d9745", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 331} +{"timestamp": "2026-06-16T15:22:05", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 334, "message": "Unexpected error during health check", "logloom_node_id": "ll:7fca919ec8da", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 334} +{"timestamp": "2026-06-16T15:22:06", "level": "ERROR", "logger": "elastro.core.ingest.engine", "module": "engine", "function": "ingest", "line": 191, "message": "Aborting: max error threshold (5) reached", "logloom_node_id": "ll:89b9a61ebf74", "logloom_module": "core.ingest.engine", "logloom_function": "ingest", "logloom_tags": ["error"], "logloom_file": "elastro/core/ingest/engine.py", "logloom_line": 191} +{"timestamp": "2026-06-16T15:22:14", "level": "ERROR", "logger": "elastro.health.manager", "module": "manager", "function": "cluster_state", "line": 154, "message": "Cluster state request failed: Mock.keys() returned a non-iterable (type Mock)"} +{"timestamp": "2026-06-16T15:22:14", "level": "ERROR", "logger": "elastro.health.collectors.health_report", "module": "health_report", "function": "collect", "line": 163, "message": "Health report collector failed: connection refused"} +{"timestamp": "2026-06-16T15:22:14", "level": "ERROR", "logger": "elastro.health.collectors.nodes", "module": "nodes", "function": "collect", "line": 49, "message": "Nodes collector failed: nodes down"} +{"timestamp": "2026-06-16T15:22:14", "level": "ERROR", "logger": "elastro.health.collectors.snapshots", "module": "snapshots", "function": "collect", "line": 78, "message": "Snapshots collector failed: forbidden"} +{"timestamp": "2026-06-16T15:22:15", "level": "ERROR", "logger": "elastro.server.routes.indices", "module": "indices", "function": "get_unhealthy_indices", "line": 103, "message": "Failed to explain allocation for index=logs-000001: explain failed", "logloom_node_id": "ll:04bd9ca1a046", "logloom_module": "server.routes.indices", "logloom_function": "get_unhealthy_indices", "logloom_tags": ["error", "http"], "logloom_file": "elastro/server/routes/indices.py", "logloom_line": 99} +{"timestamp": "2026-06-16T15:22:16", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:22:16", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:23", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:25", "level": "ERROR", "logger": "elastro.server.routes.indices", "module": "indices", "function": "get_unhealthy_indices", "line": 103, "message": "Failed to explain allocation for index=logs-000001: explain failed", "logloom_node_id": "ll:04bd9ca1a046", "logloom_module": "server.routes.indices", "logloom_function": "get_unhealthy_indices", "logloom_tags": ["error", "http"], "logloom_file": "elastro/server/routes/indices.py", "logloom_line": 99} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "exists", "line": 103, "message": "Failed to check if index 'logs-2024' exists: Client is not connected. Call connect() first.", "logloom_node_id": "ll:6c55b43c8209", "logloom_module": "elastro.core.index", "logloom_function": "exists", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 103} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.index", "module": "index", "function": "allocation_explain", "line": 336, "message": "Failed to explain allocation for index 'locked-index': Client is not connected. Call connect() first.", "logloom_node_id": "ll:116d2fc542fb", "logloom_module": "elastro.core.index", "logloom_function": "allocation_explain", "logloom_tags": ["error"], "logloom_file": "elastro/core/index.py", "logloom_line": 336} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 222, "message": "Connection failed: Connection error", "logloom_node_id": "ll:4aa4882295bb", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 222} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 215, "message": "Ping failed during connection attempt", "logloom_node_id": "ll:ad55e51afd04", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error", "retry"], "logloom_file": "elastro/core/client.py", "logloom_line": 215} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "connect", "line": 232, "message": "Unexpected connection error: Failed to connect to Elasticsearch", "logloom_node_id": "ll:3c3f2f4dc6a1", "logloom_module": "elastro.core.client", "logloom_function": "connect", "logloom_tags": ["error"], "logloom_file": "elastro/core/client.py", "logloom_line": 232} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 326, "message": "Lost connection during health check", "logloom_node_id": "ll:e075c1163938", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 326} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 331, "message": "Transport error during health check: 500", "logloom_node_id": "ll:40accf9d9745", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 331} +{"timestamp": "2026-06-16T15:22:26", "level": "ERROR", "logger": "elastro.core.client", "module": "client", "function": "health_check", "line": 334, "message": "Unexpected error during health check", "logloom_node_id": "ll:7fca919ec8da", "logloom_module": "elastro.core.client", "logloom_function": "health_check", "logloom_tags": ["error", "validation"], "logloom_file": "elastro/core/client.py", "logloom_line": 334} +{"timestamp": "2026-06-16T15:22:27", "level": "ERROR", "logger": "elastro.core.ingest.engine", "module": "engine", "function": "ingest", "line": 191, "message": "Aborting: max error threshold (5) reached", "logloom_node_id": "ll:89b9a61ebf74", "logloom_module": "core.ingest.engine", "logloom_function": "ingest", "logloom_tags": ["error"], "logloom_file": "elastro/core/ingest/engine.py", "logloom_line": 191} diff --git a/elastro/cli/commands/health.py b/elastro/cli/commands/health.py index 818e1eb..0b29482 100644 --- a/elastro/cli/commands/health.py +++ b/elastro/cli/commands/health.py @@ -158,14 +158,25 @@ def health_group() -> None: default=False, help="With --fix, show planned API calls without executing", ) -@click.option("--yes", is_flag=True, default=False, help="With --fix, auto-confirm CONFIRM actions") +@click.option( + "--yes", + is_flag=True, + default=False, + help="With --fix, auto-confirm CONFIRM actions", +) @click.option( "--force", is_flag=True, default=False, help="With --fix and --yes, allow DESTRUCTIVE actions in non-interactive mode", ) -@click.option("--index", "index_pattern", type=str, default=None, help="Limit fixes to an index pattern") +@click.option( + "--index", + "index_pattern", + type=str, + default=None, + help="Limit fixes to an index pattern", +) @click.option( "--action", "action_filter", @@ -173,7 +184,12 @@ def health_group() -> None: default=None, help="Limit fixes to a single remediation action", ) -@click.option("--target-replicas", type=int, default=None, help="Explicit replica target for reduce_replicas") +@click.option( + "--target-replicas", + type=int, + default=None, + help="Explicit replica target for reduce_replicas", +) @click.option( "--history/--no-history", default=False, @@ -386,7 +402,13 @@ def _render_health_fix( default=False, help="With --yes, allow DESTRUCTIVE actions in non-interactive mode", ) -@click.option("--index", "index_pattern", type=str, default=None, help="Limit fixes to an index pattern") +@click.option( + "--index", + "index_pattern", + type=str, + default=None, + help="Limit fixes to an index pattern", +) @click.option( "--action", "action_filter", @@ -846,7 +868,9 @@ def health_nodes( @health_group.command("shards") -@click.option("--index", type=str, default=None, help="Limit to a specific index pattern") +@click.option( + "--index", type=str, default=None, help="Limit to a specific index pattern" +) @click.option( "--analyze", is_flag=True, @@ -977,7 +1001,9 @@ def health_shards( default=False, help="Show only indices with failed or stuck ILM lifecycle steps", ) -@click.option("--index", "index_pattern", type=str, default=None, help="Limit to an index pattern") +@click.option( + "--index", "index_pattern", type=str, default=None, help="Limit to an index pattern" +) @click.pass_context def health_ilm( ctx: click.Context, @@ -1203,9 +1229,7 @@ def health_rollback_list(ctx: click.Context, last: int) -> None: f"{record.action_id} {record.applied_at.isoformat()}" ) else: - payload = { - "rollbacks": [record.model_dump(mode="json") for record in records] - } + payload = {"rollbacks": [record.model_dump(mode="json") for record in records]} click.echo(format_output(payload, output_format=output_fmt)) @@ -1251,10 +1275,14 @@ def _run_health_rollback( rollback_id, dry_run, ) - audit = None if dry_run else HealthAuditLogger( - client, - profile=_cli_profile(ctx), - host=_client_host(client), + audit = ( + None + if dry_run + else HealthAuditLogger( + client, + profile=_cli_profile(ctx), + host=_client_host(client), + ) ) executor = RemediationExecutor( client, @@ -1395,4 +1423,4 @@ def health_report( click.echo(format_output(result, output_format="json")) except OperationError as e: click.echo(f"Error fetching health report: {str(e)}", err=True) - raise SystemExit(1) from e \ No newline at end of file + raise SystemExit(1) from e diff --git a/elastro/cli/commands/index.py b/elastro/cli/commands/index.py index 18e27e1..b2f4a24 100644 --- a/elastro/cli/commands/index.py +++ b/elastro/cli/commands/index.py @@ -186,7 +186,9 @@ def update_index(client: ElasticsearchClient, name: str, settings: Any) -> None: help="Preview deletion without executing (scriptable with -o json)", ) @click.pass_obj -def delete_index(client: ElasticsearchClient, name: str, force: bool, dry_run: bool) -> None: +def delete_index( + client: ElasticsearchClient, name: str, force: bool, dry_run: bool +) -> None: """ Delete an index. @@ -210,7 +212,11 @@ def delete_index(client: ElasticsearchClient, name: str, force: bool, dry_run: b elastro index delete my-logs --force ``` """ - from elastro.cli.deletion import emit_delete_preview, preview_index_delete, should_prompt_for_delete + from elastro.cli.deletion import ( + emit_delete_preview, + preview_index_delete, + should_prompt_for_delete, + ) if dry_run: emit_delete_preview(preview_index_delete(client, name)) @@ -337,7 +343,9 @@ def list_indices(client: ElasticsearchClient, pattern: str) -> None: color = ( "green" if health == "green" - else "yellow" if health == "yellow" else "red" + else "yellow" + if health == "yellow" + else "red" ) table.add_row( @@ -603,7 +611,9 @@ def fix_indices(client: ElasticsearchClient) -> None: fix_result = run_health_fix( client, interactive=True, - confirm=lambda prompt, default: Confirm.ask(f" {prompt}", default=default), + confirm=lambda prompt, default: Confirm.ask( + f" {prompt}", default=default + ), prompt=lambda message: Prompt.ask(f" {message}"), ) render_fix_run_result(fix_result, output_format="table") diff --git a/elastro/cli/commands/snapshot.py b/elastro/cli/commands/snapshot.py index 031990f..ef8f069 100644 --- a/elastro/cli/commands/snapshot.py +++ b/elastro/cli/commands/snapshot.py @@ -140,7 +140,10 @@ def delete_repository( elastro -o json snapshot repo delete my_backup --dry-run ``` """ - from elastro.cli.deletion import emit_delete_preview, preview_snapshot_repository_delete + from elastro.cli.deletion import ( + emit_delete_preview, + preview_snapshot_repository_delete, + ) if dry_run: emit_delete_preview(preview_snapshot_repository_delete(client, name)) @@ -307,7 +310,7 @@ def restore_snapshot( if not repository: console.print("\nAvailable Repositories:") for i, r in enumerate(repo_names): - console.print(f" {i+1}. {r}") + console.print(f" {i + 1}. {r}") repo_idx = int(Prompt.ask("Select Repository", default="1")) - 1 repository = repo_names[repo_idx] @@ -334,7 +337,7 @@ def restore_snapshot( for i in range(limit): s = snaps[i] console.print( - f" {i+1}. {s['snapshot']} ({s['state']}, {len(s['indices'])} indices)" + f" {i + 1}. {s['snapshot']} ({s['state']}, {len(s['indices'])} indices)" ) snap_idx = int(Prompt.ask("Select Snapshot", default="1")) - 1 diff --git a/elastro/cli/deletion.py b/elastro/cli/deletion.py index 8ec8c2e..f9a7b43 100644 --- a/elastro/cli/deletion.py +++ b/elastro/cli/deletion.py @@ -79,10 +79,15 @@ def emit_delete_preview(preview: DeletePreview) -> None: click.echo(f" {key}: {value}") return - click.echo(format_output(delete_preview_payload(preview), output_format=output_fmt), nl=False) + click.echo( + format_output(delete_preview_payload(preview), output_format=output_fmt), + nl=False, + ) -def should_prompt_for_delete(*, dry_run: bool, force: bool = False, yes: bool = False) -> bool: +def should_prompt_for_delete( + *, dry_run: bool, force: bool = False, yes: bool = False +) -> bool: """Return True when interactive confirmation is required.""" if dry_run: return False @@ -325,7 +330,9 @@ def preview_script_delete(client: ElasticsearchClient, script_id: str) -> Delete ) -def preview_pipeline_delete(client: ElasticsearchClient, pipeline_id: str) -> DeletePreview: +def preview_pipeline_delete( + client: ElasticsearchClient, pipeline_id: str +) -> DeletePreview: exists = False try: client._ensure_connected() @@ -344,4 +351,4 @@ def preview_pipeline_delete(client: ElasticsearchClient, pipeline_id: str) -> De if exists else f"Ingest pipeline '{pipeline_id}' does not exist; delete would fail." ), - ) \ No newline at end of file + ) diff --git a/elastro/core/errors.py b/elastro/core/errors.py index 73887e1..6c11759 100644 --- a/elastro/core/errors.py +++ b/elastro/core/errors.py @@ -48,6 +48,10 @@ class ElasticIndexError(OperationError): pass +# Backward-compatible alias used by tests and docs. +IndexError = ElasticIndexError + + class DocumentError(OperationError): """Raised when a document operation fails.""" diff --git a/elastro/core/ilm.py b/elastro/core/ilm.py index 79e0e0c..7727bb1 100644 --- a/elastro/core/ilm.py +++ b/elastro/core/ilm.py @@ -182,4 +182,6 @@ def retry_lifecycle(self, index: str) -> bool: return True except Exception as e: logger.error("Failed to retry ILM lifecycle for '%s': %s", index, e) - raise OperationError(f"Failed to retry ILM lifecycle for '{index}': {e}") from e + raise OperationError( + f"Failed to retry ILM lifecycle for '{index}': {e}" + ) from e diff --git a/elastro/core/logger.py b/elastro/core/logger.py index 0f1709f..0c009e7 100644 --- a/elastro/core/logger.py +++ b/elastro/core/logger.py @@ -120,9 +120,7 @@ def configure_logging(log_level: Optional[str] = None) -> logging.Logger: ) package_logger.addHandler(file_handler) except Exception: - sys.stderr.write( - f"Warning: Could not set up file logging to {LOG_FILE_PATH}\n" - ) + sys.stderr.write(f"Warning: Could not set up file logging to {LOG_FILE_PATH}\n") if _logloom_handler is not None: try: @@ -158,4 +156,4 @@ def get_logger(name: str, log_level: Optional[str] = None) -> logging.Logger: logger.handlers.clear() if log_level is not None: logger.setLevel(_resolve_level(log_level)) - return logger \ No newline at end of file + return logger diff --git a/elastro/health/__init__.py b/elastro/health/__init__.py index d724085..6a2b757 100644 --- a/elastro/health/__init__.py +++ b/elastro/health/__init__.py @@ -9,7 +9,10 @@ Severity, ) from elastro.health.assessor import HealthAssessor -from elastro.health.collectors.health_report import HealthReportCollector, map_indicators +from elastro.health.collectors.health_report import ( + HealthReportCollector, + map_indicators, +) from elastro.health.manager import HealthManager from elastro.health.remediation import RemediationCatalog, RemediationExecutor @@ -26,4 +29,4 @@ "RemediationExecutor", "RemediationSafety", "Severity", -] \ No newline at end of file +] diff --git a/elastro/health/assessor.py b/elastro/health/assessor.py index f6c3290..ad47806 100644 --- a/elastro/health/assessor.py +++ b/elastro/health/assessor.py @@ -119,7 +119,8 @@ def run( title="Health report unavailable", status=FindingStatus.SKIPPED, severity=Severity.LOW, - summary=result.error or "Health report collector was skipped.", + summary=result.error + or "Health report collector was skipped.", source="collector", ) ) @@ -131,7 +132,9 @@ def run( if result.name == "health_report": report_data = result.data raw_health_report = report_data.get("report") - cluster_name = report_data.get("cluster_name", cluster_name) or cluster_name + cluster_name = ( + report_data.get("cluster_name", cluster_name) or cluster_name + ) indicators = report_data.get("indicators", {}) overall_score = compute_weighted_score(indicators) findings.extend(non_passing_findings(report_data.get("findings", []))) @@ -225,9 +228,7 @@ def run( overall_score = max(0, overall_score - deduction) collector_data = { - result.name: result.data - for result in results - if result.status == "ok" + result.name: result.data for result in results if result.status == "ok" } disk_data = collector_data.get("disk") or {} if isinstance(disk_data, dict) and disk_data.get("cluster_settings"): @@ -395,4 +396,4 @@ def _run_collectors( results.append(result) - return results \ No newline at end of file + return results diff --git a/elastro/health/audit.py b/elastro/health/audit.py index 4ac7c40..a3106a6 100644 --- a/elastro/health/audit.py +++ b/elastro/health/audit.py @@ -156,4 +156,4 @@ def log_rollback( record.session_id, payload, cluster_name=record.cluster_name, - ) \ No newline at end of file + ) diff --git a/elastro/health/collectors/__init__.py b/elastro/health/collectors/__init__.py index a2fa6f2..97e42d1 100644 --- a/elastro/health/collectors/__init__.py +++ b/elastro/health/collectors/__init__.py @@ -12,4 +12,4 @@ "Collector", "CollectorRegistry", "CollectorResult", -] \ No newline at end of file +] diff --git a/elastro/health/collectors/base.py b/elastro/health/collectors/base.py index ccf4314..2c60b79 100644 --- a/elastro/health/collectors/base.py +++ b/elastro/health/collectors/base.py @@ -102,4 +102,4 @@ def run( ) results.append(result) - return results \ No newline at end of file + return results diff --git a/elastro/health/collectors/cluster.py b/elastro/health/collectors/cluster.py index 9f035a7..50d1d4e 100644 --- a/elastro/health/collectors/cluster.py +++ b/elastro/health/collectors/cluster.py @@ -48,4 +48,4 @@ def collect(self, ctx: CollectContext) -> CollectorResult: ) except OperationError as exc: logger.error("Pending tasks collector failed: %s", exc) - return CollectorResult(name=self.name, status="error", error=str(exc)) \ No newline at end of file + return CollectorResult(name=self.name, status="error", error=str(exc)) diff --git a/elastro/health/collectors/disk.py b/elastro/health/collectors/disk.py index 74bc59e..d2d3e62 100644 --- a/elastro/health/collectors/disk.py +++ b/elastro/health/collectors/disk.py @@ -52,7 +52,8 @@ def collect(self, ctx: CollectContext) -> CollectorResult: return CollectorResult( name=self.name, status="error", - error=nodes_result.error or "Failed to collect node filesystem stats", + error=nodes_result.error + or "Failed to collect node filesystem stats", ) nodes = nodes_result.data.get("nodes", {}) @@ -166,7 +167,11 @@ def disk_watermark_findings( used_pct = node["used_percent"] node_name = node["node_name"] if used_pct >= flood_threshold: - status, severity, stage = FindingStatus.FAIL, Severity.CRITICAL, "flood-stage" + status, severity, stage = ( + FindingStatus.FAIL, + Severity.CRITICAL, + "flood-stage", + ) score_impact = 15 elif used_pct >= high_threshold: status, severity, stage = FindingStatus.WARN, Severity.HIGH, "high" @@ -247,4 +252,4 @@ def read_only_block_findings( metadata={"index_name": index_name}, ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/collectors/health_report.py b/elastro/health/collectors/health_report.py index 4338312..6b2bd22 100644 --- a/elastro/health/collectors/health_report.py +++ b/elastro/health/collectors/health_report.py @@ -141,9 +141,7 @@ class HealthReportCollector: def collect(self, ctx: CollectContext) -> CollectorResult: version = ctx.es_version or "unknown" if not supports_health_report(version): - logger.info( - "Skipping health_report collector: ES %s < 8.7", version - ) + logger.info("Skipping health_report collector: ES %s < 8.7", version) return CollectorResult( name=self.name, status="skipped", @@ -286,4 +284,4 @@ def _suggest_command( return "elastro cluster settings --enable-routing all" if "replica" in action_lower: return "elastro health fix" - return "elastro health report --verbose" \ No newline at end of file + return "elastro health report --verbose" diff --git a/elastro/health/collectors/ilm.py b/elastro/health/collectors/ilm.py index f8f8518..da373d5 100644 --- a/elastro/health/collectors/ilm.py +++ b/elastro/health/collectors/ilm.py @@ -9,7 +9,13 @@ from elastro.core.index import IndexManager from elastro.core.logger import get_logger from elastro.health.collectors.base import CollectContext, CollectorResult -from elastro.health.models import Finding, FindingStatus, RemediationAction, RemediationSafety, Severity +from elastro.health.models import ( + Finding, + FindingStatus, + RemediationAction, + RemediationSafety, + Severity, +) logger = get_logger(__name__) @@ -168,4 +174,4 @@ def _lifecycle_issue(explain: Dict[str, Any]) -> Optional[str]: if managed and explain.get("phase") and not explain.get("step"): return "ILM policy assigned but no active lifecycle step" - return None \ No newline at end of file + return None diff --git a/elastro/health/collectors/mappings.py b/elastro/health/collectors/mappings.py index 37e6127..30a82e3 100644 --- a/elastro/health/collectors/mappings.py +++ b/elastro/health/collectors/mappings.py @@ -77,4 +77,4 @@ def _summaries( summaries.append(summarize_index_mapping(name, body)) if len(summaries) >= max_indices: break - return summaries \ No newline at end of file + return summaries diff --git a/elastro/health/collectors/nodes.py b/elastro/health/collectors/nodes.py index 91a842a..9b5708d 100644 --- a/elastro/health/collectors/nodes.py +++ b/elastro/health/collectors/nodes.py @@ -80,4 +80,4 @@ def _normalize_nodes( "breakers": body.get("breakers", {}), "thread_pool": body.get("thread_pool", {}), } - return nodes \ No newline at end of file + return nodes diff --git a/elastro/health/collectors/security.py b/elastro/health/collectors/security.py index 624c04f..24b3a85 100644 --- a/elastro/health/collectors/security.py +++ b/elastro/health/collectors/security.py @@ -6,7 +6,13 @@ from elastro.core.logger import get_logger from elastro.health.collectors.base import CollectContext, CollectorResult -from elastro.health.models import Finding, FindingStatus, RemediationAction, RemediationSafety, Severity +from elastro.health.models import ( + Finding, + FindingStatus, + RemediationAction, + RemediationSafety, + Severity, +) logger = get_logger(__name__) @@ -149,9 +155,7 @@ def _overprivileged_role_findings(roles: Dict[str, Any]) -> List[Finding]: if not isinstance(role_body, dict): continue cluster_privs = { - str(item).lower() - for item in (role_body.get("cluster") or []) - if item + str(item).lower() for item in (role_body.get("cluster") or []) if item } if not cluster_privs.intersection(_PRIVILEGED_CLUSTER_PRIVS): continue @@ -179,4 +183,4 @@ def _overprivileged_role_findings(roles: Dict[str, Any]) -> List[Finding]: ), ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/collectors/shards.py b/elastro/health/collectors/shards.py index ca43596..51ca6ff 100644 --- a/elastro/health/collectors/shards.py +++ b/elastro/health/collectors/shards.py @@ -140,4 +140,4 @@ def _analysis_to_dict(analysis: ShardAnalysis) -> Dict[str, Any]: } for item in analysis.undersharded[:50] ], - } \ No newline at end of file + } diff --git a/elastro/health/collectors/snapshots.py b/elastro/health/collectors/snapshots.py index 7371940..486b9dc 100644 --- a/elastro/health/collectors/snapshots.py +++ b/elastro/health/collectors/snapshots.py @@ -76,4 +76,4 @@ def collect(self, ctx: CollectContext) -> CollectorResult: ) except OperationError as exc: logger.error("Snapshots collector failed: %s", exc) - return CollectorResult(name=self.name, status="error", error=str(exc)) \ No newline at end of file + return CollectorResult(name=self.name, status="error", error=str(exc)) diff --git a/elastro/health/config.py b/elastro/health/config.py index 6c2bf93..69c5c2b 100644 --- a/elastro/health/config.py +++ b/elastro/health/config.py @@ -42,4 +42,4 @@ "payload": {"type": "object", "enabled": True}, } } -} \ No newline at end of file +} diff --git a/elastro/health/disk_blocks.py b/elastro/health/disk_blocks.py index 71f6fd3..307e591 100644 --- a/elastro/health/disk_blocks.py +++ b/elastro/health/disk_blocks.py @@ -60,4 +60,4 @@ def filter_indices(indices: List[str], pattern: Optional[str]) -> List[str]: """Filter index names with shell-style glob matching.""" if not pattern: return indices - return [name for name in indices if fnmatch.fnmatchcase(name, pattern)] \ No newline at end of file + return [name for name in indices if fnmatch.fnmatchcase(name, pattern)] diff --git a/elastro/health/exit_policy.py b/elastro/health/exit_policy.py index 74f8b29..ffbc865 100644 --- a/elastro/health/exit_policy.py +++ b/elastro/health/exit_policy.py @@ -176,4 +176,4 @@ def combine_exit_codes(*codes: int) -> int: return 0 if 2 in non_zero: return 2 - return max(non_zero) \ No newline at end of file + return max(non_zero) diff --git a/elastro/health/finding_guides/__init__.py b/elastro/health/finding_guides/__init__.py index 628c6d5..659e21a 100644 --- a/elastro/health/finding_guides/__init__.py +++ b/elastro/health/finding_guides/__init__.py @@ -2,4 +2,4 @@ from elastro.health.finding_guides.oversharding import build_oversharding_guide -__all__ = ["build_oversharding_guide"] \ No newline at end of file +__all__ = ["build_oversharding_guide"] diff --git a/elastro/health/finding_guides/oversharding.py b/elastro/health/finding_guides/oversharding.py index ceb2100..f7d7400 100644 --- a/elastro/health/finding_guides/oversharding.py +++ b/elastro/health/finding_guides/oversharding.py @@ -181,7 +181,7 @@ def build_oversharding_guide( ), "commands": [ "elastro health ilm --stuck-only -o table", - "curl -X GET \"$ES_HOST/_data_stream/*?filter_path=data_streams.name\"", + 'curl -X GET "$ES_HOST/_data_stream/*?filter_path=data_streams.name"', ], }, { @@ -194,9 +194,9 @@ def build_oversharding_guide( ), "commands": [ "curl -X PUT \"$ES_HOST/my-index/_settings\" -H 'Content-Type: application/json' " - '-d \'{"index.blocks.write": true}\'', - "curl -X POST \"$ES_HOST/my-index/_shrink/my-index-shrunk\" " - '-H \'Content-Type: application/json\' ' + "-d '{\"index.blocks.write\": true}'", + 'curl -X POST "$ES_HOST/my-index/_shrink/my-index-shrunk" ' + "-H 'Content-Type: application/json' " '-d \'{"settings": {"index.number_of_shards": 1}}\'', ], }, @@ -220,7 +220,7 @@ def build_oversharding_guide( "to production writers." ), "commands": [ - "curl -X GET \"$ES_HOST/_index_template/my-template\"", + 'curl -X GET "$ES_HOST/_index_template/my-template"', ], }, ] @@ -255,5 +255,7 @@ def build_oversharding_guide( if es_version: sections["elasticsearch_version"] = es_version - affected = [str(item.get("index", "")) for item in top_indices[:10] if item.get("index")] - return _detail_text(sections), {"detail_sections": sections}, affected \ No newline at end of file + affected = [ + str(item.get("index", "")) for item in top_indices[:10] if item.get("index") + ] + return _detail_text(sections), {"detail_sections": sections}, affected diff --git a/elastro/health/formatters/__init__.py b/elastro/health/formatters/__init__.py index f4d8fd3..bc32bd3 100644 --- a/elastro/health/formatters/__init__.py +++ b/elastro/health/formatters/__init__.py @@ -8,4 +8,4 @@ "format_assessment_json", "format_assessment_table", "format_assessment_yaml", -] \ No newline at end of file +] diff --git a/elastro/health/formatters/history_table.py b/elastro/health/formatters/history_table.py index 70148a3..ffe0892 100644 --- a/elastro/health/formatters/history_table.py +++ b/elastro/health/formatters/history_table.py @@ -139,11 +139,7 @@ def format_fleet_summary_table(rows: List[Dict[str, Any]], *, window: str) -> st for row in rows: latest_score = row.get("latest_score") - score_text = ( - f"{int(latest_score)}/100" - if latest_score is not None - else "—" - ) + score_text = f"{int(latest_score)}/100" if latest_score is not None else "—" table.add_row( str(row.get("cluster_name", "unknown")), score_text, @@ -154,4 +150,4 @@ def format_fleet_summary_table(rows: List[Dict[str, Any]], *, window: str) -> st ) console.print(table) - return buf.getvalue() \ No newline at end of file + return buf.getvalue() diff --git a/elastro/health/formatters/hotspots_table.py b/elastro/health/formatters/hotspots_table.py index 7b6fa04..8090076 100644 --- a/elastro/health/formatters/hotspots_table.py +++ b/elastro/health/formatters/hotspots_table.py @@ -39,4 +39,4 @@ def format_hotspots_table(hotspots: List[Dict[str, Any]]) -> str: console.print(table) console.print() - return buf.getvalue() \ No newline at end of file + return buf.getvalue() diff --git a/elastro/health/formatters/ilm_table.py b/elastro/health/formatters/ilm_table.py index 6edcf35..2c6ce73 100644 --- a/elastro/health/formatters/ilm_table.py +++ b/elastro/health/formatters/ilm_table.py @@ -41,4 +41,4 @@ def _row(values: tuple[str, str, str, str]) -> str: _row(tuple("-" * width for width in widths)), # type: ignore[arg-type] ] lines.extend(_row(row) for row in rows) - return "\n".join(lines) + "\n" \ No newline at end of file + return "\n".join(lines) + "\n" diff --git a/elastro/health/formatters/json_fmt.py b/elastro/health/formatters/json_fmt.py index ed8f485..0078388 100644 --- a/elastro/health/formatters/json_fmt.py +++ b/elastro/health/formatters/json_fmt.py @@ -17,4 +17,4 @@ def format_assessment_json( payload: Dict[str, Any] = report.model_dump(mode="json") if not include_raw: payload.pop("raw_health_report", None) - return json.dumps(payload, indent=2, default=str) \ No newline at end of file + return json.dumps(payload, indent=2, default=str) diff --git a/elastro/health/formatters/lint_table.py b/elastro/health/formatters/lint_table.py index c7bbffc..fe565a1 100644 --- a/elastro/health/formatters/lint_table.py +++ b/elastro/health/formatters/lint_table.py @@ -9,7 +9,11 @@ from rich.console import Console from rich.table import Table -from elastro.health.formatters.table import _SEVERITY_ORDER, _SEVERITY_STYLE, _STATUS_STYLE +from elastro.health.formatters.table import ( + _SEVERITY_ORDER, + _SEVERITY_STYLE, + _STATUS_STYLE, +) from elastro.health.models import Finding @@ -24,7 +28,11 @@ def format_lint_table(findings: List[Finding]) -> str: sorted_findings = sorted( findings, - key=lambda item: (_SEVERITY_ORDER.get(item.severity, 99), item.category, item.id), + key=lambda item: ( + _SEVERITY_ORDER.get(item.severity, 99), + item.category, + item.id, + ), ) table = Table( @@ -59,4 +67,4 @@ def format_lint_table(findings: List[Finding]) -> str: console.print() console.print(table) console.print() - return buf.getvalue() \ No newline at end of file + return buf.getvalue() diff --git a/elastro/health/formatters/nodes_table.py b/elastro/health/formatters/nodes_table.py index f88067f..9094960 100644 --- a/elastro/health/formatters/nodes_table.py +++ b/elastro/health/formatters/nodes_table.py @@ -105,4 +105,4 @@ def format_nodes_table( console.print(table) console.print() - return buf.getvalue() \ No newline at end of file + return buf.getvalue() diff --git a/elastro/health/formatters/render.py b/elastro/health/formatters/render.py index fabb0b1..2711771 100644 --- a/elastro/health/formatters/render.py +++ b/elastro/health/formatters/render.py @@ -26,4 +26,4 @@ def render_assessment( ) if output_format == "yaml": return format_assessment_yaml(report, include_raw=include_raw) - return format_assessment_json(report, include_raw=include_raw) \ No newline at end of file + return format_assessment_json(report, include_raw=include_raw) diff --git a/elastro/health/formatters/shards_table.py b/elastro/health/formatters/shards_table.py index 88c2a31..a64cec4 100644 --- a/elastro/health/formatters/shards_table.py +++ b/elastro/health/formatters/shards_table.py @@ -70,4 +70,4 @@ def format_shard_analyze_table(analysis: Dict[str, Any]) -> str: console.print(table) console.print() - return buf.getvalue() \ No newline at end of file + return buf.getvalue() diff --git a/elastro/health/formatters/table.py b/elastro/health/formatters/table.py index eb61859..e48ed58 100644 --- a/elastro/health/formatters/table.py +++ b/elastro/health/formatters/table.py @@ -166,4 +166,4 @@ def format_assessment_table( "\n[dim]Tip: run with --detail (or --detail shards.oversharded) " "for remediation guidance.[/]\n" ) - return output \ No newline at end of file + return output diff --git a/elastro/health/formatters/yaml_fmt.py b/elastro/health/formatters/yaml_fmt.py index 9281554..41c1fbf 100644 --- a/elastro/health/formatters/yaml_fmt.py +++ b/elastro/health/formatters/yaml_fmt.py @@ -16,4 +16,4 @@ def format_assessment_yaml( payload = report.model_dump(mode="json") if not include_raw: payload.pop("raw_health_report", None) - return yaml.dump(payload, default_flow_style=False, sort_keys=False) \ No newline at end of file + return yaml.dump(payload, default_flow_style=False, sort_keys=False) diff --git a/elastro/health/history.py b/elastro/health/history.py index c2980cc..8ed8955 100644 --- a/elastro/health/history.py +++ b/elastro/health/history.py @@ -69,7 +69,9 @@ def parse_window(window: str) -> timedelta: """Parse a duration string such as ``7d``, ``24h``, or ``30m``.""" match = _WINDOW_PATTERN.match(str(window).strip()) if not match: - raise ValueError(f"Invalid window '{window}'. Use formats like 7d, 24h, or 30m.") + raise ValueError( + f"Invalid window '{window}'. Use formats like 7d, 24h, or 30m." + ) amount = int(match.group(1)) unit = match.group(2).lower() return timedelta(**{_WINDOW_UNITS[unit]: amount}) @@ -302,11 +304,7 @@ def history_cluster_summary( f"Failed to query fleet history summary index={history_index}: {exc}" ) from exc - buckets = ( - response.get("aggregations", {}) - .get("by_cluster", {}) - .get("buckets", []) - ) + buckets = response.get("aggregations", {}).get("by_cluster", {}).get("buckets", []) summary: List[Dict[str, Any]] = [] for bucket in buckets: if not isinstance(bucket, dict): @@ -328,4 +326,4 @@ def history_cluster_summary( } ) summary.sort(key=lambda item: str(item.get("cluster_name", ""))) - return summary \ No newline at end of file + return summary diff --git a/elastro/health/ilm_status.py b/elastro/health/ilm_status.py index 384117b..1bf998f 100644 --- a/elastro/health/ilm_status.py +++ b/elastro/health/ilm_status.py @@ -52,11 +52,7 @@ def list_ilm_indices( if index_pattern: import fnmatch - targets = { - name - for name in targets - if fnmatch.fnmatchcase(name, index_pattern) - } + targets = {name for name in targets if fnmatch.fnmatchcase(name, index_pattern)} health_by_index = { str(entry.get("index", "")): str(entry.get("health", "unknown")).lower() @@ -103,11 +99,7 @@ def list_stuck_ilm_indices( if index_pattern: import fnmatch - targets = { - name - for name in targets - if fnmatch.fnmatchcase(name, index_pattern) - } + targets = {name for name in targets if fnmatch.fnmatchcase(name, index_pattern)} health_by_index = { str(entry.get("index", "")): str(entry.get("health", "unknown")).lower() @@ -137,5 +129,7 @@ def list_stuck_ilm_indices( ) ) - logger.info("ILM stuck scan complete: targets=%s stuck=%s", len(targets), len(stuck)) - return stuck \ No newline at end of file + logger.info( + "ILM stuck scan complete: targets=%s stuck=%s", len(targets), len(stuck) + ) + return stuck diff --git a/elastro/health/lint.py b/elastro/health/lint.py index 4977254..7b57f9c 100644 --- a/elastro/health/lint.py +++ b/elastro/health/lint.py @@ -14,10 +14,20 @@ from elastro.health.collectors.security import SecurityCollector from elastro.health.collectors.shards import ShardsCollector from elastro.health.mappings import is_system_index -from elastro.health.models import Finding, FindingStatus, RemediationAction, RemediationSafety, Severity +from elastro.health.models import ( + Finding, + FindingStatus, + RemediationAction, + RemediationSafety, + Severity, +) from elastro.health.rules.mapping_explosion import mapping_explosion_findings from elastro.health.rules.engine import RuleContext -from elastro.health.shards import DEFAULT_OVERSHARD_THRESHOLD_MB, DEFAULT_UNDERSHARD_THRESHOLD_GB, format_bytes +from elastro.health.shards import ( + DEFAULT_OVERSHARD_THRESHOLD_MB, + DEFAULT_UNDERSHARD_THRESHOLD_GB, + format_bytes, +) logger = get_logger(__name__) @@ -330,9 +340,7 @@ def _lint_shards(shard_data: dict) -> List[Finding]: status=FindingStatus.WARN, severity=Severity.MEDIUM, score_impact=min(undersharded * 2, 10), - summary=( - f"{undersharded} shard(s) exceed {format_bytes(threshold)}." - ), + summary=(f"{undersharded} shard(s) exceed {format_bytes(threshold)}."), source="lint", remediation=RemediationAction( id="analyze_shards", @@ -352,4 +360,4 @@ def _parse_int(value: object, *, default: Optional[int] = None) -> Optional[int] try: return int(str(value).strip()) except (TypeError, ValueError): - return default \ No newline at end of file + return default diff --git a/elastro/health/manager.py b/elastro/health/manager.py index d96084b..796f359 100644 --- a/elastro/health/manager.py +++ b/elastro/health/manager.py @@ -176,9 +176,7 @@ def verify_repository(self, repository: str) -> bool: e, exc_info=True, ) - raise OperationError( - f"Failed to verify repository {repository}: {str(e)}" - ) + raise OperationError(f"Failed to verify repository {repository}: {str(e)}") def index_stats(self, index: Optional[str] = None) -> Dict[str, Any]: logger.debug("Fetching index stats index=%s", index) @@ -217,4 +215,4 @@ def diagnose(self) -> Dict[str, Any]: return diagnostic except Exception as e: logger.error("Diagnostics summary failed: %s", e, exc_info=True) - raise OperationError(f"Failed to perform diagnostics: {str(e)}") \ No newline at end of file + raise OperationError(f"Failed to perform diagnostics: {str(e)}") diff --git a/elastro/health/mappings.py b/elastro/health/mappings.py index 930312b..6384a13 100644 --- a/elastro/health/mappings.py +++ b/elastro/health/mappings.py @@ -84,4 +84,4 @@ def select_user_indices( names.append(name) if len(names) >= limit: break - return sorted(names) \ No newline at end of file + return sorted(names) diff --git a/elastro/health/models.py b/elastro/health/models.py index 51ac006..62955a6 100644 --- a/elastro/health/models.py +++ b/elastro/health/models.py @@ -61,9 +61,7 @@ class AssessmentReport(BaseModel): session_id: str = Field(default_factory=lambda: str(uuid4())) cluster_name: str = "unknown" elasticsearch_version: str = "unknown" - assessed_at: datetime = Field( - default_factory=lambda: datetime.now(timezone.utc) - ) + assessed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) duration_ms: int = 0 overall_score: int = 0 overall_status: FindingStatus = FindingStatus.UNKNOWN @@ -86,4 +84,4 @@ def score_to_status(score: int) -> FindingStatus: def cluster_status_to_score(status: str) -> int: """Map Elasticsearch cluster health color to a baseline score.""" - return {"green": 100, "yellow": 70, "red": 30}.get(status, 0) \ No newline at end of file + return {"green": 100, "yellow": 70, "red": 30}.get(status, 0) diff --git a/elastro/health/remediation/__init__.py b/elastro/health/remediation/__init__.py index 7d3e936..030dbdd 100644 --- a/elastro/health/remediation/__init__.py +++ b/elastro/health/remediation/__init__.py @@ -23,4 +23,4 @@ "RemediationPlanner", "RemediationResult", "run_health_fix", -] \ No newline at end of file +] diff --git a/elastro/health/remediation/actions/__init__.py b/elastro/health/remediation/actions/__init__.py index 6cbfe2a..424ef42 100644 --- a/elastro/health/remediation/actions/__init__.py +++ b/elastro/health/remediation/actions/__init__.py @@ -32,4 +32,4 @@ "planned_reroute_failed", "reduce_replicas", "reroute_failed", -] \ No newline at end of file +] diff --git a/elastro/health/remediation/actions/clear_read_only.py b/elastro/health/remediation/actions/clear_read_only.py index 206cd1d..b31a363 100644 --- a/elastro/health/remediation/actions/clear_read_only.py +++ b/elastro/health/remediation/actions/clear_read_only.py @@ -21,8 +21,10 @@ def planned_clear_read_only(index_name: str) -> str: ) -def clear_read_only(index_manager: IndexManager, index_name: str, **kwargs: object) -> str: +def clear_read_only( + index_manager: IndexManager, index_name: str, **kwargs: object +) -> str: """Allow writes and deletes again on a flood-stage blocked index.""" logger.info("Clearing read_only_allow_delete block for index %s", index_name) index_manager.update(index_name, _CLEAR_READ_ONLY_SETTINGS) - return f"read_only_allow_delete block cleared for {index_name}" \ No newline at end of file + return f"read_only_allow_delete block cleared for {index_name}" diff --git a/elastro/health/remediation/actions/clear_routing_filters.py b/elastro/health/remediation/actions/clear_routing_filters.py index 56251f2..1e287d8 100644 --- a/elastro/health/remediation/actions/clear_routing_filters.py +++ b/elastro/health/remediation/actions/clear_routing_filters.py @@ -20,14 +20,11 @@ def planned_clear_routing_filters(index_name: str) -> str: - return ( - f"PUT /{index_name}/_settings " - f"body={{'index': {_ROUTING_FILTER_SETTINGS}}}" - ) + return f"PUT /{index_name}/_settings body={{'index': {_ROUTING_FILTER_SETTINGS}}}" def clear_routing_filters(index_manager: IndexManager, index_name: str) -> str: """Remove custom routing allocation filters from an index.""" logger.info("Clearing routing allocation filters for index %s", index_name) index_manager.update(index_name, {"index": _ROUTING_FILTER_SETTINGS}) - return f"Custom routing allocation filters cleared for {index_name}" \ No newline at end of file + return f"Custom routing allocation filters cleared for {index_name}" diff --git a/elastro/health/remediation/actions/ilm_retry.py b/elastro/health/remediation/actions/ilm_retry.py index 449278e..18c6a0f 100644 --- a/elastro/health/remediation/actions/ilm_retry.py +++ b/elastro/health/remediation/actions/ilm_retry.py @@ -17,4 +17,4 @@ def ilm_retry(index_manager: IndexManager, index_name: str, **kwargs: object) -> """Retry ILM execution for the given index.""" logger.info("Retrying ILM lifecycle for index %s", index_name) IlmManager(index_manager._client).retry_lifecycle(index_name) - return f"ILM retry requested for {index_name}" \ No newline at end of file + return f"ILM retry requested for {index_name}" diff --git a/elastro/health/remediation/actions/reduce_replicas.py b/elastro/health/remediation/actions/reduce_replicas.py index 0a48a65..1021bc6 100644 --- a/elastro/health/remediation/actions/reduce_replicas.py +++ b/elastro/health/remediation/actions/reduce_replicas.py @@ -19,10 +19,7 @@ def planned_reduce_replicas( ) -> str: target = target_replicas if target_replicas is not None else 0 payload = _payload(api_mode=api_mode, target_replicas=target) - return ( - f"PUT /{index_name}/_settings " - f"body={payload}" - ) + return f"PUT /{index_name}/_settings body={payload}" def _payload(*, api_mode: bool, target_replicas: int) -> Dict[str, Any]: @@ -115,7 +112,5 @@ def reduce_replicas( ignore_unavailable=True, ) if api_mode: - return ( - f"Replicas reduced to {target} and auto-expand disabled for {index_name}" - ) - return f"Replicas reduced to {target} for {index_name}" \ No newline at end of file + return f"Replicas reduced to {target} and auto-expand disabled for {index_name}" + return f"Replicas reduced to {target} for {index_name}" diff --git a/elastro/health/remediation/actions/reroute_failed.py b/elastro/health/remediation/actions/reroute_failed.py index ed122a3..e0ce244 100644 --- a/elastro/health/remediation/actions/reroute_failed.py +++ b/elastro/health/remediation/actions/reroute_failed.py @@ -17,4 +17,4 @@ def reroute_failed(index_manager: IndexManager) -> str: """Ask Elasticsearch to retry failed shard allocations.""" logger.info("Requesting cluster reroute with retry_failed=true") index_manager.reroute(retry_failed=True) - return "Cluster rerouted to retry failed shards" \ No newline at end of file + return "Cluster rerouted to retry failed shards" diff --git a/elastro/health/remediation/catalog.py b/elastro/health/remediation/catalog.py index dc713de..ec4012b 100644 --- a/elastro/health/remediation/catalog.py +++ b/elastro/health/remediation/catalog.py @@ -171,4 +171,4 @@ def triggers_remediation_scan(cls, command: Optional[str]) -> bool: "elastro health assess --fix", "elastro index fix", "elastro cluster allocation", - } \ No newline at end of file + } diff --git a/elastro/health/remediation/diagnosis.py b/elastro/health/remediation/diagnosis.py index a41f679..79fc7d4 100644 --- a/elastro/health/remediation/diagnosis.py +++ b/elastro/health/remediation/diagnosis.py @@ -16,7 +16,10 @@ def detect_routing_filter_fault(explain_result: Dict[str, Any]) -> bool: for node_decision in explain_result.get("node_allocation_decisions", []): for decider in node_decision.get("deciders", []): explanation = decider.get("explanation", "") - if decider.get("decider") == "filter" and "index.routing.allocation" in explanation: + if ( + decider.get("decider") == "filter" + and "index.routing.allocation" in explanation + ): return True return False @@ -113,9 +116,7 @@ def list_unhealthy_indices(index_manager: IndexManager) -> List[Dict[str, Any]]: """Return cat indices entries that are yellow or red.""" indices = index_manager.list() unhealthy = [ - idx - for idx in indices - if idx.get("health", "green") in {"yellow", "red"} + idx for idx in indices if idx.get("health", "green") in {"yellow", "red"} ] logger.info("Found %s unhealthy index(es)", len(unhealthy)) return unhealthy @@ -162,4 +163,4 @@ def diagnose_unhealthy_indices(index_manager: IndexManager) -> List[IndexDiagnos len(diagnoses), sum(1 for d in diagnoses if d.suggested_action_id), ) - return diagnoses \ No newline at end of file + return diagnoses diff --git a/elastro/health/remediation/display.py b/elastro/health/remediation/display.py index a5cbbc7..b1889e8 100644 --- a/elastro/health/remediation/display.py +++ b/elastro/health/remediation/display.py @@ -7,7 +7,10 @@ import rich_click as click from elastro.health.models import RemediationSafety -from elastro.health.remediation.dry_run import fix_run_payload, remediation_result_payload +from elastro.health.remediation.dry_run import ( + fix_run_payload, + remediation_result_payload, +) from elastro.health.remediation.models import ( FixRunResult, IndexDiagnosis, @@ -150,17 +153,11 @@ def render_remediation_summary( ) return - heading = ( - "Planned remediations (dry-run)" - if dry_run - else "Remediation results" - ) + heading = "Planned remediations (dry-run)" if dry_run else "Remediation results" click.echo(f"\n{heading}:") for diagnosis in diagnoses: if not diagnosis.suggested_action_id: - click.echo( - f" - {diagnosis.index_name}: no automated quick fix available" - ) + click.echo(f" - {diagnosis.index_name}: no automated quick fix available") continue matching = [ @@ -194,9 +191,7 @@ def _render_single_result( label = planned.label if planned else result.action_id scope = result.index_name or "cluster" if dry_run: - click.echo( - f" - {scope} [{result.action_id}]: {result.planned_api_call}" - ) + click.echo(f" - {scope} [{result.action_id}]: {result.planned_api_call}") return if result.executed and result.success: @@ -247,4 +242,4 @@ def _render_legacy_result( f" ✗ {diagnosis.index_name}: {result.message}", fg="red", ) - ) \ No newline at end of file + ) diff --git a/elastro/health/remediation/dry_run.py b/elastro/health/remediation/dry_run.py index 6f24f9a..8a3a440 100644 --- a/elastro/health/remediation/dry_run.py +++ b/elastro/health/remediation/dry_run.py @@ -21,9 +21,7 @@ def summarize_fix_run(result: FixRunResult) -> Dict[str, Any]: """Build a stable scripting summary for fix / plan / dry-run passes.""" executed = sum(1 for item in result.results if item.executed) previewed = sum( - 1 - for item in result.results - if item.dry_run and item.planned_api_call + 1 for item in result.results if item.dry_run and item.planned_api_call ) return { "preview_only": is_preview_mode( @@ -61,4 +59,4 @@ def assert_no_executions(results: Iterable[RemediationResult]) -> None: actions = ", ".join( f"{item.action_id}:{item.index_name or 'cluster'}" for item in executed ) - raise RuntimeError(f"Dry-run executed mutations: {actions}") \ No newline at end of file + raise RuntimeError(f"Dry-run executed mutations: {actions}") diff --git a/elastro/health/remediation/executor.py b/elastro/health/remediation/executor.py index bb35732..3456f88 100644 --- a/elastro/health/remediation/executor.py +++ b/elastro/health/remediation/executor.py @@ -501,4 +501,4 @@ def remediate_unhealthy_indices( "Remediation pass complete: planned_or_executed=%s", len(results), ) - return results \ No newline at end of file + return results diff --git a/elastro/health/remediation/fix.py b/elastro/health/remediation/fix.py index 217a2e1..54c370a 100644 --- a/elastro/health/remediation/fix.py +++ b/elastro/health/remediation/fix.py @@ -12,7 +12,11 @@ from elastro.health.audit import HealthAuditLogger from elastro.health.remediation.diagnosis import diagnose_unhealthy_indices from elastro.health.remediation.executor import RemediationExecutor -from elastro.health.remediation.models import FixRunResult, PlannedAction, RemediationResult +from elastro.health.remediation.models import ( + FixRunResult, + PlannedAction, + RemediationResult, +) from elastro.health.remediation.catalog import CATALOG_ACTION_IDS from elastro.health.remediation.planner import RemediationPlanner from elastro.health.remediation.dry_run import is_preview_mode @@ -117,10 +121,7 @@ def run_health_fix( executed_cluster: set[str] = set() for planned_action in planned: - if ( - planned_action.dedupe_key - and planned_action.dedupe_key in executed_cluster - ): + if planned_action.dedupe_key and planned_action.dedupe_key in executed_cluster: logger.info( "Skipping duplicate cluster action %s", planned_action.action_id, @@ -166,4 +167,4 @@ def run_health_fix( dry_run=dry_run, plan_only=False, session_id=resolved_session, - ) \ No newline at end of file + ) diff --git a/elastro/health/remediation/models.py b/elastro/health/remediation/models.py index db2e40a..3090a2b 100644 --- a/elastro/health/remediation/models.py +++ b/elastro/health/remediation/models.py @@ -60,5 +60,3 @@ class FixRunResult(BaseModel): dry_run: bool = False plan_only: bool = False session_id: Optional[str] = None - - diff --git a/elastro/health/remediation/planner.py b/elastro/health/remediation/planner.py index 773f800..ed0997c 100644 --- a/elastro/health/remediation/planner.py +++ b/elastro/health/remediation/planner.py @@ -242,4 +242,4 @@ def plan_explicit( action_id, len(planned), ) - return planned \ No newline at end of file + return planned diff --git a/elastro/health/remediation/rollback.py b/elastro/health/remediation/rollback.py index 4279b3f..76d3f82 100644 --- a/elastro/health/remediation/rollback.py +++ b/elastro/health/remediation/rollback.py @@ -98,9 +98,7 @@ def list_records(self, *, limit: int = 50) -> List[RollbackRecord]: for path in self._root.glob("*.json"): try: records.append( - RollbackRecord.model_validate_json( - path.read_text(encoding="utf-8") - ) + RollbackRecord.model_validate_json(path.read_text(encoding="utf-8")) ) except Exception as exc: logger.warning( @@ -211,4 +209,4 @@ def apply_rollback( return ( f"Restored settings for '{record.index_name}' " f"from rollback {record.rollback_id}" - ) \ No newline at end of file + ) diff --git a/elastro/health/remediation/safety.py b/elastro/health/remediation/safety.py index c316101..e92a130 100644 --- a/elastro/health/remediation/safety.py +++ b/elastro/health/remediation/safety.py @@ -52,9 +52,7 @@ def describe_impact( "May change cluster or index state. Review the planned API call.", ) if action_id == "reduce_replicas" and index_name and target_replicas is not None: - return ( - f"Set '{index_name}' replicas to {target_replicas}. {base}" - ) + return f"Set '{index_name}' replicas to {target_replicas}. {base}" if index_name: return f"Apply to index '{index_name}'. {base}" return base @@ -62,11 +60,7 @@ def describe_impact( def build_confirm_prompt(planned: PlannedAction) -> str: """Build the primary yes/no confirmation prompt.""" - scope = ( - f" for '{planned.index_name}'" - if planned.index_name - else " (cluster-wide)" - ) + scope = f" for '{planned.index_name}'" if planned.index_name else " (cluster-wide)" return f"Apply {planned.label}{scope}?" @@ -221,4 +215,4 @@ def _interactive_confirm(self, planned: PlannedAction) -> ConfirmDecision: impact_line = f"\n\nImpact: {planned.impact}" if planned.impact else "" if self._confirm(f"{prompt}{impact_line}", default): return ConfirmDecision(execute=True) - return ConfirmDecision(execute=False, message="Skipped by user") \ No newline at end of file + return ConfirmDecision(execute=False, message="Skipped by user") diff --git a/elastro/health/rules/__init__.py b/elastro/health/rules/__init__.py index 48090d5..697a199 100644 --- a/elastro/health/rules/__init__.py +++ b/elastro/health/rules/__init__.py @@ -19,4 +19,4 @@ "oversharding_findings", "persistent_yellow_findings", "replica_misconfig_findings", -] \ No newline at end of file +] diff --git a/elastro/health/rules/backup_policy.py b/elastro/health/rules/backup_policy.py index 6c8ea53..9426ea8 100644 --- a/elastro/health/rules/backup_policy.py +++ b/elastro/health/rules/backup_policy.py @@ -43,8 +43,10 @@ def backup_policy_findings(ctx: RuleContext) -> List[Finding]: " 3. Verify with: elastro snapshot list (or GET _snapshot)\n" " 4. Test restore on a non-production cluster quarterly" ), - affected_resources=[ctx.cluster_name] if ctx.cluster_name != "unknown" else [], + affected_resources=[ctx.cluster_name] + if ctx.cluster_name != "unknown" + else [], source="rule", metadata={"repository_count": 0}, ) - ] \ No newline at end of file + ] diff --git a/elastro/health/rules/circuit_breaker.py b/elastro/health/rules/circuit_breaker.py index f1a9b91..93d128f 100644 --- a/elastro/health/rules/circuit_breaker.py +++ b/elastro/health/rules/circuit_breaker.py @@ -127,4 +127,4 @@ def circuit_breaker_rule(ctx: RuleContext) -> List[Finding]: nodes_data = ctx.collector_data.get("nodes") or {} if not nodes_data: return [] - return circuit_breaker_findings(nodes_data) \ No newline at end of file + return circuit_breaker_findings(nodes_data) diff --git a/elastro/health/rules/cluster_blocks.py b/elastro/health/rules/cluster_blocks.py index 85ee5d4..fca25be 100644 --- a/elastro/health/rules/cluster_blocks.py +++ b/elastro/health/rules/cluster_blocks.py @@ -49,7 +49,9 @@ def cluster_block_findings(ctx: RuleContext) -> List[Finding]: "Writes or metadata updates may be blocked cluster-wide." ), detail=_block_detail(block_id, block_data), - affected_resources=[ctx.cluster_name] if ctx.cluster_name != "unknown" else [], + affected_resources=[ctx.cluster_name] + if ctx.cluster_name != "unknown" + else [], source="rule", metadata={"block_id": block_id, "block": block_data}, ) @@ -67,4 +69,4 @@ def _block_detail(block_id: str, block_data: Dict[str, Any]) -> str: " • read_only: remove cluster.blocks.read_only setting after fixing root cause\n" " • metadata: resolve master election or cluster state issues first\n" " • elastro health assess --detail (disk / watermark findings)" - ) \ No newline at end of file + ) diff --git a/elastro/health/rules/cluster_capacity.py b/elastro/health/rules/cluster_capacity.py index 1676923..7a77b1b 100644 --- a/elastro/health/rules/cluster_capacity.py +++ b/elastro/health/rules/cluster_capacity.py @@ -90,8 +90,12 @@ def shard_limit_findings( f"{open_shards:,} shards open ({pct}% of estimated " f"{cluster_limit:,} limit = {max_per_node:,}/node × {data_nodes} data nodes)." ), - detail=_shard_limit_detail(open_shards, cluster_limit, max_per_node, data_nodes), - affected_resources=[ctx.cluster_name] if ctx.cluster_name != "unknown" else [], + detail=_shard_limit_detail( + open_shards, cluster_limit, max_per_node, data_nodes + ), + affected_resources=[ctx.cluster_name] + if ctx.cluster_name != "unknown" + else [], source="rule", metadata={ "open_shards": open_shards, @@ -124,4 +128,4 @@ def _shard_limit_detail( def shard_limit_rule(ctx: RuleContext) -> List[Finding]: - return shard_limit_findings(ctx) \ No newline at end of file + return shard_limit_findings(ctx) diff --git a/elastro/health/rules/cpu_pressure.py b/elastro/health/rules/cpu_pressure.py index acc0969..60f537a 100644 --- a/elastro/health/rules/cpu_pressure.py +++ b/elastro/health/rules/cpu_pressure.py @@ -91,4 +91,4 @@ def cpu_pressure_rule(ctx: RuleContext) -> List[Finding]: nodes_data = ctx.collector_data.get("nodes") or {} if not nodes_data: return [] - return cpu_pressure_findings(nodes_data) \ No newline at end of file + return cpu_pressure_findings(nodes_data) diff --git a/elastro/health/rules/engine.py b/elastro/health/rules/engine.py index 9c864e8..caa4369 100644 --- a/elastro/health/rules/engine.py +++ b/elastro/health/rules/engine.py @@ -85,4 +85,4 @@ def evaluate(self, ctx: RuleContext) -> List[Finding]: len(emitted), ) findings.extend(emitted) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/rules/hotspots.py b/elastro/health/rules/hotspots.py index 9ed028d..00ac6ce 100644 --- a/elastro/health/rules/hotspots.py +++ b/elastro/health/rules/hotspots.py @@ -132,4 +132,4 @@ def hotspot_findings( metadata=hotspot, ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/rules/jvm.py b/elastro/health/rules/jvm.py index b94a4eb..02eb3e3 100644 --- a/elastro/health/rules/jvm.py +++ b/elastro/health/rules/jvm.py @@ -89,4 +89,4 @@ def jvm_rule(ctx: RuleContext) -> List[Finding]: nodes_data = ctx.collector_data.get("nodes") or {} if not nodes_data: return [] - return jvm_pressure_findings(nodes_data) \ No newline at end of file + return jvm_pressure_findings(nodes_data) diff --git a/elastro/health/rules/mapping_explosion.py b/elastro/health/rules/mapping_explosion.py index 6c6d927..5527812 100644 --- a/elastro/health/rules/mapping_explosion.py +++ b/elastro/health/rules/mapping_explosion.py @@ -6,7 +6,13 @@ from elastro.core.logger import get_logger from elastro.health.mappings import DEFAULT_FIELD_WARN_RATIO -from elastro.health.models import Finding, FindingStatus, RemediationAction, RemediationSafety, Severity +from elastro.health.models import ( + Finding, + FindingStatus, + RemediationAction, + RemediationSafety, + Severity, +) from elastro.health.rules.engine import RuleContext logger = get_logger(__name__) @@ -74,4 +80,4 @@ def mapping_explosion_findings( ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/rules/master_topology.py b/elastro/health/rules/master_topology.py index 6785357..15241f9 100644 --- a/elastro/health/rules/master_topology.py +++ b/elastro/health/rules/master_topology.py @@ -103,4 +103,4 @@ def master_topology_findings(ctx: RuleContext) -> List[Finding]: ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/rules/oversharding.py b/elastro/health/rules/oversharding.py index 7130426..efd7829 100644 --- a/elastro/health/rules/oversharding.py +++ b/elastro/health/rules/oversharding.py @@ -6,9 +6,19 @@ from elastro.core.logger import get_logger from elastro.health.finding_guides.oversharding import build_oversharding_guide -from elastro.health.models import Finding, FindingStatus, RemediationAction, RemediationSafety, Severity +from elastro.health.models import ( + Finding, + FindingStatus, + RemediationAction, + RemediationSafety, + Severity, +) from elastro.health.rules.engine import RuleContext -from elastro.health.shards import DEFAULT_OVERSHARD_THRESHOLD_MB, DEFAULT_UNDERSHARD_THRESHOLD_GB, format_bytes +from elastro.health.shards import ( + DEFAULT_OVERSHARD_THRESHOLD_MB, + DEFAULT_UNDERSHARD_THRESHOLD_GB, + format_bytes, +) logger = get_logger(__name__) @@ -99,4 +109,4 @@ def oversharding_findings(ctx: RuleContext) -> List[Finding]: ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/rules/persistent_yellow.py b/elastro/health/rules/persistent_yellow.py index 152d780..6b49e73 100644 --- a/elastro/health/rules/persistent_yellow.py +++ b/elastro/health/rules/persistent_yellow.py @@ -38,10 +38,9 @@ def _record_indicates_yellow(record: Dict[str, Any]) -> bool: finding_id = str(finding.get("id", "")) if finding_id.startswith("cluster.status.yellow"): return True - if ( - finding.get("indicator") == "shards_availability" - and finding.get("status") in {"warn", "fail"} - ): + if finding.get("indicator") == "shards_availability" and finding.get( + "status" + ) in {"warn", "fail"}: return True overall_status = str(record.get("overall_status", "")).lower() @@ -117,4 +116,4 @@ def persistent_yellow_findings( "assessments_in_streak": len(yellow_streak), }, ) - ] \ No newline at end of file + ] diff --git a/elastro/health/rules/replica.py b/elastro/health/rules/replica.py index 857ac43..1cd6cf6 100644 --- a/elastro/health/rules/replica.py +++ b/elastro/health/rules/replica.py @@ -117,4 +117,4 @@ def replica_misconfig_findings(ctx: RuleContext) -> List[Finding]: ) ) - return findings \ No newline at end of file + return findings diff --git a/elastro/health/rules/shards_unassigned.py b/elastro/health/rules/shards_unassigned.py index f30883a..fa37674 100644 --- a/elastro/health/rules/shards_unassigned.py +++ b/elastro/health/rules/shards_unassigned.py @@ -79,4 +79,4 @@ def _unassigned_detail(count: int) -> str: " 2. elastro health shards --analyze -o table\n" " 3. elastro health fix --dry-run (replica/reroute remediations)\n" " 4. Free disk or raise watermarks if allocation is disk-blocked" - ) \ No newline at end of file + ) diff --git a/elastro/health/rules/thread_pool.py b/elastro/health/rules/thread_pool.py index 5490228..d306dd7 100644 --- a/elastro/health/rules/thread_pool.py +++ b/elastro/health/rules/thread_pool.py @@ -91,4 +91,4 @@ def thread_pool_rule(ctx: RuleContext) -> List[Finding]: nodes_data = ctx.collector_data.get("nodes") or {} if not nodes_data: return [] - return thread_pool_findings(nodes_data) \ No newline at end of file + return thread_pool_findings(nodes_data) diff --git a/elastro/health/scoring.py b/elastro/health/scoring.py index b0e8b0d..59898e0 100644 --- a/elastro/health/scoring.py +++ b/elastro/health/scoring.py @@ -61,4 +61,4 @@ def compute_weighted_score( def compute_fallback_score(cluster_status: str) -> int: """Fallback score when _health_report is unavailable.""" - return cluster_status_to_score(cluster_status) \ No newline at end of file + return cluster_status_to_score(cluster_status) diff --git a/elastro/health/shards.py b/elastro/health/shards.py index eb2b9f4..6c92473 100644 --- a/elastro/health/shards.py +++ b/elastro/health/shards.py @@ -138,9 +138,7 @@ def analyze_shards( undersharded.append(record) avg_bytes = ( - sum(item.store_bytes for item in measured) / len(measured) - if measured - else 0.0 + sum(item.store_bytes for item in measured) / len(measured) if measured else 0.0 ) logger.debug( "Shard analysis: total=%s measured=%s oversharded=%s undersharded=%s", @@ -160,4 +158,4 @@ def analyze_shards( undersharded=undersharded, overshard_threshold_bytes=overshard_bytes, undershard_threshold_bytes=undershard_bytes, - ) \ No newline at end of file + ) diff --git a/elastro/health/trends.py b/elastro/health/trends.py index 2fc29ed..45d4ce2 100644 --- a/elastro/health/trends.py +++ b/elastro/health/trends.py @@ -133,7 +133,9 @@ def recurring_finding_ids( threshold = max(1, int(len(records) * _RECURRING_THRESHOLD)) recurring = [ finding_id - for finding_id, count in sorted(counts.items(), key=lambda item: (-item[1], item[0])) + for finding_id, count in sorted( + counts.items(), key=lambda item: (-item[1], item[0]) + ) if count >= threshold ] if finding_filter: @@ -262,4 +264,4 @@ def compute_trends_from_records( 1 for record in records if _record_indicates_yellow(record) ), source=source, - ) \ No newline at end of file + ) diff --git a/elastro/health/version.py b/elastro/health/version.py index d4e743d..7a53b9d 100644 --- a/elastro/health/version.py +++ b/elastro/health/version.py @@ -21,4 +21,4 @@ def parse_version(version: str) -> Tuple[int, int, int]: def supports_health_report(version: str) -> bool: """Return True when the cluster supports GET _health_report (8.7+).""" major, minor, _ = parse_version(version) - return (major, minor) >= (8, 7) \ No newline at end of file + return (major, minor) >= (8, 7) diff --git a/elastro/server/__init__.py b/elastro/server/__init__.py index 19c4c3f..9985944 100644 --- a/elastro/server/__init__.py +++ b/elastro/server/__init__.py @@ -248,9 +248,7 @@ def launch_gui_process() -> str: state = json.load(f) if _should_reuse_gui_server(state): - return ( - f"http://127.0.0.1:{state['port']}?token={state['token']}" - ) + return f"http://127.0.0.1:{state['port']}?token={state['token']}" pid = state.get("pid") if pid: diff --git a/elastro/server/cluster_inventory.py b/elastro/server/cluster_inventory.py index f486ef2..d3c91e7 100644 --- a/elastro/server/cluster_inventory.py +++ b/elastro/server/cluster_inventory.py @@ -38,9 +38,11 @@ def _count_kibana_objects(es: Any, object_type: str) -> Optional[int]: index=",".join(index_names), query={"term": {"type": object_type}}, ) - count = response.get("count") if isinstance(response, dict) else getattr( - response, "body", {} - ).get("count") + count = ( + response.get("count") + if isinstance(response, dict) + else getattr(response, "body", {}).get("count") + ) return int(count or 0) except Exception as exc: logger.debug( @@ -96,7 +98,9 @@ def fetch_cluster_inventory(es: Any) -> Dict[str, Any]: unassigned_shards += 1 cluster_stats = es.cluster.stats() - indices_stats = cluster_stats.get("indices", {}) if isinstance(cluster_stats, dict) else {} + indices_stats = ( + cluster_stats.get("indices", {}) if isinstance(cluster_stats, dict) else {} + ) docs = indices_stats.get("docs", {}) if isinstance(indices_stats, dict) else {} store = indices_stats.get("store", {}) if isinstance(indices_stats, dict) else {} total_docs = int(docs.get("count", 0) or 0) @@ -121,7 +125,11 @@ def fetch_cluster_inventory(es: Any) -> Dict[str, Any]: template_count = 0 try: composable = es.indices.get_index_template(name="*") - templates = composable.get("index_templates", []) if isinstance(composable, dict) else [] + templates = ( + composable.get("index_templates", []) + if isinstance(composable, dict) + else [] + ) template_count += len(templates) except Exception as exc: logger.debug("Composable index templates unavailable: %s", exc) @@ -180,4 +188,4 @@ def fetch_cluster_inventory(es: Any) -> Dict[str, Any]: "repository_count": len(repos), "repositories": repos, }, - } \ No newline at end of file + } diff --git a/elastro/server/health_cache.py b/elastro/server/health_cache.py index d2805d0..f4a7abf 100644 --- a/elastro/server/health_cache.py +++ b/elastro/server/health_cache.py @@ -73,4 +73,4 @@ def clear_cache(cluster_name: Optional[str] = None) -> None: _history.clear() return _assessment_cache.pop(cluster_name, None) - _history.pop(cluster_name, None) \ No newline at end of file + _history.pop(cluster_name, None) diff --git a/elastro/server/routes/health.py b/elastro/server/routes/health.py index 7336f95..61d81c3 100644 --- a/elastro/server/routes/health.py +++ b/elastro/server/routes/health.py @@ -43,9 +43,7 @@ def _health_assessment_settings(read_config: Any) -> Dict[str, Any]: assessment = {} return { "enable_history": bool(assessment.get("enable_history", False)), - "history_index": str( - assessment.get("history_index", DEFAULT_HISTORY_INDEX) - ), + "history_index": str(assessment.get("history_index", DEFAULT_HISTORY_INDEX)), } @@ -145,9 +143,7 @@ def _open_findings(report: AssessmentReport) -> List[Dict[str, Any]]: FindingStatus.UNKNOWN, } findings = [ - finding - for finding in report.findings - if finding.status in open_statuses + finding for finding in report.findings if finding.status in open_statuses ] findings.sort( key=lambda item: ( @@ -506,4 +502,4 @@ def apply_health_fix( ) raise HTTPException(status_code=500, detail=str(exc)) from exc - return router \ No newline at end of file + return router diff --git a/elastro/utils/health.py b/elastro/utils/health.py index 3790d61..b21f61f 100644 --- a/elastro/utils/health.py +++ b/elastro/utils/health.py @@ -5,4 +5,4 @@ from elastro.health.manager import HealthManager -__all__ = ["HealthManager"] \ No newline at end of file +__all__ = ["HealthManager"] diff --git a/examples/datastreams.py b/examples/datastreams.py index 8a93327..a9f2fb5 100644 --- a/examples/datastreams.py +++ b/examples/datastreams.py @@ -181,7 +181,7 @@ def index_documents_to_datastream(client, datastream_name, count=10): # Create a log entry log_entry = { "@timestamp": timestamp_str, - "message": f"Sample log message #{i+1}", + "message": f"Sample log message #{i + 1}", "level": log_levels[i % len(log_levels)], "service": services[i % len(services)], "host": hosts[i % len(hosts)], diff --git a/pyproject.toml b/pyproject.toml index 0dacfc0..39a5be8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,10 +53,8 @@ dev = [ "pytest>=7.0.0", "pytest-cov>=3.0.0", "httpx2>=0.1.0", - "black>=22.0.0", - "isort>=5.0.0", + "ruff>=0.9.0", "mypy>=0.9.0", - "flake8>=4.0.0", ] ingest-sql = [ "sqlalchemy>=2.0.0", @@ -72,18 +70,16 @@ exclude = ["tests*", "examples*"] [tool.setuptools.package-data] "elastro" = ["gui/**/*"] -[tool.black] +[tool.ruff] line-length = 88 -target-version = ["py39"] -extend-exclude = ''' -/( - .*daemon\.py -)/ -''' +target-version = "py39" +extend-exclude = ["elastro/core/daemon.py"] -[tool.isort] -profile = "black" -line_length = 88 +[tool.ruff.lint] +select = ["E", "F", "I"] + +[tool.ruff.lint.isort] +known-first-party = ["elastro"] [tool.mypy] python_version = "3.10" diff --git a/tests/integration/ingest_stress/ingest_evaluator.py b/tests/integration/ingest_stress/ingest_evaluator.py index ef85a21..c512255 100644 --- a/tests/integration/ingest_stress/ingest_evaluator.py +++ b/tests/integration/ingest_stress/ingest_evaluator.py @@ -6,9 +6,12 @@ WORKSPACE = Path(__file__).parent.parent.parent.parent SCRATCH = WORKSPACE / "tests" / "integration" / "ingest_stress" / "data" -ARTIFACTS = Path("/Users/jonathandoughty/.gemini/antigravity/brain/e8ee424a-0e30-4543-a356-0ca3e27add02/artifacts") +ARTIFACTS = Path( + "/Users/jonathandoughty/.gemini/antigravity/brain/e8ee424a-0e30-4543-a356-0ca3e27add02/artifacts" +) BIN = WORKSPACE / ".venv" / "bin" / "elastro" + # 1. Generate Data def generate_data(): SCRATCH.mkdir(exist_ok=True, parents=True) @@ -18,27 +21,32 @@ def generate_data(): with open(SCRATCH / "simple_clean.csv", "w") as f: f.write("id,name,age,active\n") for i in range(1, 101): - f.write(f"{i},User {i},{20+i},true\n") + f.write(f"{i},User {i},{20 + i},true\n") # Complex/Dirty CSV with open(SCRATCH / "complex_dirty.csv", "w") as f: f.write("id,name,tags,metadata,score,active\n") f.write('1,Alice,"admin,user","{""login"": ""2023""}",99.5,true\n') - f.write('2,Bob,,,"invalid_score",false\n') # Sparse, invalid type - f.write('3,"Charlie, Jr.",, ,,\n') # Commas in quotes, lots of empty + f.write('2,Bob,,,"invalid_score",false\n') # Sparse, invalid type + f.write('3,"Charlie, Jr.",, ,,\n') # Commas in quotes, lots of empty f.write('4,Dave,"guest",,0,\n') # Simple JSON - simple_json = [{"id": i, "name": f"User {i}", "age": 20+i} for i in range(1, 101)] + simple_json = [{"id": i, "name": f"User {i}", "age": 20 + i} for i in range(1, 101)] with open(SCRATCH / "simple_clean.json", "w") as f: json.dump(simple_json, f) # Complex/Dirty JSON complex_json = [ - {"id": 1, "name": "Alice", "settings": {"theme": "dark", "notifications": True}, "roles": ["admin", "user"]}, - {"id": 2, "name": "Bob", "settings": None, "roles": []}, # Sparse - {"id": "3_str", "name": 12345}, # Type mismatches relative to 1 and 2 - {"id": 4, "deep": {"nested": {"array": [1, 2, {"three": 3}]}}} # Deeply nested + { + "id": 1, + "name": "Alice", + "settings": {"theme": "dark", "notifications": True}, + "roles": ["admin", "user"], + }, + {"id": 2, "name": "Bob", "settings": None, "roles": []}, # Sparse + {"id": "3_str", "name": 12345}, # Type mismatches relative to 1 and 2 + {"id": 4, "deep": {"nested": {"array": [1, 2, {"three": 3}]}}}, # Deeply nested ] with open(SCRATCH / "complex_dirty.json", "w") as f: json.dump(complex_json, f) @@ -46,14 +54,17 @@ def generate_data(): # Simple NDJSON with open(SCRATCH / "simple_clean.ndjson", "w") as f: for i in range(1, 101): - f.write(json.dumps({"id": i, "name": f"User {i}", "age": 20+i}) + "\n") + f.write(json.dumps({"id": i, "name": f"User {i}", "age": 20 + i}) + "\n") # Complex/Dirty NDJSON with open(SCRATCH / "complex_dirty.ndjson", "w") as f: f.write(json.dumps({"id": 1, "name": "Alice", "tags": ["a", "b"]}) + "\n") - f.write(json.dumps({"id": 2}) + "\n") # Sparse - f.write(json.dumps({"id": 3, "name": "Charlie", "tags": "not_an_array_anymore"}) + "\n") - f.write('{"id": 4, "broken_json": unquoted_value}\n') # Malformed line + f.write(json.dumps({"id": 2}) + "\n") # Sparse + f.write( + json.dumps({"id": 3, "name": "Charlie", "tags": "not_an_array_anymore"}) + + "\n" + ) + f.write('{"id": 4, "broken_json": unquoted_value}\n') # Malformed line # Simple SQL with open(SCRATCH / "simple_clean.sql", "w") as f: @@ -63,76 +74,111 @@ def generate_data(): # Complex/Dirty SQL with open(SCRATCH / "complex_dirty.sql", "w") as f: # Multi-row insert, sparse values (NULL) - f.write("INSERT INTO users (id, name, role, score) VALUES ('1', 'Alice', 'admin', '100'), ('2', 'Bob', NULL, '50');\n") - f.write("INSERT INTO users (id, name, role, score) VALUES ('3', 'Charlie', 'user', NULL);\n") + f.write( + "INSERT INTO users (id, name, role, score) VALUES ('1', 'Alice', 'admin', '100'), ('2', 'Bob', NULL, '50');\n" + ) + f.write( + "INSERT INTO users (id, name, role, score) VALUES ('3', 'Charlie', 'user', NULL);\n" + ) # SQL with extra spaces and quotes in strings f.write("INSERT INTO users (id, name) VALUES ('4', 'Dave O''Connor');\n") + # 2. Run Tests def run_tests(): report = ["# Elastro Ingest Edge-Case Evaluation Report\n"] - report.append("This report documents the behavior of Elastro's data ingestion engine across various formats, evaluating its resilience against sparse, dirty, and deeply nested data structures.\n\n") + report.append( + "This report documents the behavior of Elastro's data ingestion engine across various formats, evaluating its resilience against sparse, dirty, and deeply nested data structures.\n\n" + ) files_to_test = [ - "simple_clean.csv", "complex_dirty.csv", - "simple_clean.json", "complex_dirty.json", - "simple_clean.ndjson", "complex_dirty.ndjson", - "simple_clean.sql", "complex_dirty.sql" + "simple_clean.csv", + "complex_dirty.csv", + "simple_clean.json", + "complex_dirty.json", + "simple_clean.ndjson", + "complex_dirty.ndjson", + "simple_clean.sql", + "complex_dirty.sql", ] for filename in files_to_test: filepath = SCRATCH / filename fmt = filename.split(".")[-1] - + # We will use auto-map validation to see how it handles mapping inference on dirty data, # then we will do a real import with --dlq to see how it drops bad rows. - + report.append(f"## Testing: `{filename}`\n") - + # 1. Profile cmd_profile = [BIN, "ingest", "profile", str(filepath)] proc_profile = subprocess.run(cmd_profile, capture_output=True, text=True) - report.append("### Data Profiling\n```text\n" + proc_profile.stdout.strip() + "\n```\n") + report.append( + "### Data Profiling\n```text\n" + proc_profile.stdout.strip() + "\n```\n" + ) # 2. Validate (Schema Inference) # Skip validation inference for malformed NDJSON because python json.loads will hard crash the generator cmd_validate = [BIN, "ingest", "validate", str(filepath)] proc_validate = subprocess.run(cmd_validate, capture_output=True, text=True) - report.append("### Schema Inference (Validate without index)\n```text\n" + proc_validate.stdout.strip() + "\n```\n") + report.append( + "### Schema Inference (Validate without index)\n```text\n" + + proc_validate.stdout.strip() + + "\n```\n" + ) if proc_validate.stderr: - report.append("#### Errors:\n```text\n" + proc_validate.stderr.strip() + "\n```\n") + report.append( + "#### Errors:\n```text\n" + proc_validate.stderr.strip() + "\n```\n" + ) # 3. Import with DLQ idx_name = f"test-eval-{filename.replace('.', '-')}-{int(time.time())}" dlq_path = SCRATCH / f"dlq_{filename}.json" - + # We will turn on --validate so it strictly tries to coerce and drops rows if they fail - cmd_import = [BIN, "ingest", "import", str(filepath), "--index", idx_name, "--dlq", str(dlq_path)] + cmd_import = [ + BIN, + "ingest", + "import", + str(filepath), + "--index", + idx_name, + "--dlq", + str(dlq_path), + ] if fmt == "sql": cmd_import.extend(["--format", "sql"]) - + start = time.time() proc_import = subprocess.run(cmd_import, capture_output=True, text=True) elapsed = time.time() - start - - report.append(f"### Import Execution\n- **Time**: {elapsed:.2f}s\n- **Exit Code**: {proc_import.returncode}\n") + + report.append( + f"### Import Execution\n- **Time**: {elapsed:.2f}s\n- **Exit Code**: {proc_import.returncode}\n" + ) report.append("```text\n" + proc_import.stdout.strip() + "\n```\n") if proc_import.stderr: - report.append("#### Errors:\n```text\n" + proc_import.stderr.strip() + "\n```\n") + report.append( + "#### Errors:\n```text\n" + proc_import.stderr.strip() + "\n```\n" + ) if dlq_path.exists() and os.path.getsize(dlq_path) > 0: with open(dlq_path, "r") as f: dlq_contents = f.read() - report.append(f"#### Dead Letter Queue (DLQ)\n```json\n{dlq_contents.strip()}\n```\n") - + report.append( + f"#### Dead Letter Queue (DLQ)\n```json\n{dlq_contents.strip()}\n```\n" + ) + report.append("---\n") report_path = ARTIFACTS / "elastro_edge_case_report.md" with open(report_path, "w") as f: f.write("\n".join(report)) - + print(f"Tests complete. Report written to {report_path}") + if __name__ == "__main__": generate_data() run_tests() diff --git a/tests/integration/ingest_stress/stress_tester.py b/tests/integration/ingest_stress/stress_tester.py index 27f77fa..f9ee036 100644 --- a/tests/integration/ingest_stress/stress_tester.py +++ b/tests/integration/ingest_stress/stress_tester.py @@ -27,7 +27,7 @@ def generate_data(): i, f"User_{i}", random.random() * 100, - f"2026-05-12T10:00:{i%60:02d}Z", + f"2026-05-12T10:00:{i % 60:02d}Z", random.choice(["true", "false"]), ] ) @@ -70,7 +70,7 @@ def generate_data(): [ i, f"user{i}@example.com", - f"123-45-{1000+i}", + f"123-45-{1000 + i}", random.randint(20, 60), ] ) @@ -215,11 +215,13 @@ def stress_test(): ret, out, err, elapsed = run_cmd(cmd, input_data) print(f"[{name}] Code: {ret}, Time: {elapsed:.2f}s") - + # Expected failure for wizard due to TTY guard if "wizard" in name: if ret != 1 or "interactive terminal" not in out + err: - issues.append(f"{name} did not abort correctly as a non-TTY process. Exit code: {ret}") + issues.append( + f"{name} did not abort correctly as a non-TTY process. Exit code: {ret}" + ) continue if ret != 0: diff --git a/tests/integration/test_health_api.py b/tests/integration/test_health_api.py index 5a56d7d..9f1c788 100644 --- a/tests/integration/test_health_api.py +++ b/tests/integration/test_health_api.py @@ -606,4 +606,4 @@ def test_index_fix_execute_failure_returns_500( ) assert response.status_code == 500 - assert "Shard allocation still blocked" in response.json()["detail"] \ No newline at end of file + assert "Shard allocation still blocked" in response.json()["detail"] diff --git a/tests/integration/test_health_assess.py b/tests/integration/test_health_assess.py index 62b6352..b2a51d1 100644 --- a/tests/integration/test_health_assess.py +++ b/tests/integration/test_health_assess.py @@ -311,4 +311,4 @@ def test_live_assess_json_validates_schema(self, runner, live_available): payload = json.loads(result.output.strip()) report = AssessmentReport.model_validate(payload) assert report.elasticsearch_version.startswith("8.") - assert report.overall_score > 0 \ No newline at end of file + assert report.overall_score > 0 diff --git a/tests/integration/test_health_exit_codes.py b/tests/integration/test_health_exit_codes.py index beb99cc..9f9fea8 100644 --- a/tests/integration/test_health_exit_codes.py +++ b/tests/integration/test_health_exit_codes.py @@ -85,9 +85,7 @@ def test_assess_fail_on_warn_exits_two( @patch("elastro.cli.cli.ElasticsearchClient.connect") @patch("elastro.cli.commands.health._run_assessment") - def test_score_fail_on_yellow_exits_two( - self, mock_assess, mock_connect, runner - ): + def test_score_fail_on_yellow_exits_two(self, mock_assess, mock_connect, runner): mock_connect.return_value = None mock_assess.return_value = _warn_report() @@ -275,7 +273,16 @@ def test_fix_partial_failure_exits_three( ): result = runner.invoke( cli, - ["-h", "http://localhost:9205", "-o", "json", "health", "fix", "--yes", "--force"], + [ + "-h", + "http://localhost:9205", + "-o", + "json", + "health", + "fix", + "--yes", + "--force", + ], ) assert result.exit_code == 3, result.output @@ -329,4 +336,4 @@ def test_assess_fix_health_degradation_beats_partial_fix_failure( ], ) - assert result.exit_code == 2, result.output \ No newline at end of file + assert result.exit_code == 2, result.output diff --git a/tests/integration/test_health_fix.py b/tests/integration/test_health_fix.py index d17f463..c96ccfb 100644 --- a/tests/integration/test_health_fix.py +++ b/tests/integration/test_health_fix.py @@ -37,7 +37,15 @@ def test_fix_dry_run_shows_plan( result = runner.invoke( cli, - ["-h", "http://localhost:9205", "-o", "table", "health", "fix", "--dry-run"], + [ + "-h", + "http://localhost:9205", + "-o", + "table", + "health", + "fix", + "--dry-run", + ], ) assert result.exit_code == 0, result.output @@ -126,7 +134,11 @@ def test_assess_plan_only( mock_connect, runner, ): - from elastro.health.remediation.models import FixRunResult, IndexDiagnosis, PlannedAction + from elastro.health.remediation.models import ( + FixRunResult, + IndexDiagnosis, + PlannedAction, + ) from elastro.health.models import RemediationSafety mock_connect.return_value = None @@ -166,10 +178,18 @@ def test_assess_plan_only( ): result = runner.invoke( cli, - ["-h", "http://localhost:9205", "-o", "table", "health", "assess", "--plan"], + [ + "-h", + "http://localhost:9205", + "-o", + "table", + "health", + "assess", + "--plan", + ], ) assert result.exit_code == 2, result.output assert "Remediation runbook" in result.output mock_run_fix.assert_called_once() - assert mock_run_fix.call_args.kwargs.get("plan_only") is True \ No newline at end of file + assert mock_run_fix.call_args.kwargs.get("plan_only") is True diff --git a/tests/integration/test_health_ilm.py b/tests/integration/test_health_ilm.py index dcc0378..5cbb9a0 100644 --- a/tests/integration/test_health_ilm.py +++ b/tests/integration/test_health_ilm.py @@ -93,4 +93,4 @@ def test_fix_ilm_retry_dry_run( assert result.exit_code == 0, result.output mock_plan_explicit.assert_called_once() assert "logs-000042" in result.output - assert "POST /logs-000042/_ilm/retry" in result.output \ No newline at end of file + assert "POST /logs-000042/_ilm/retry" in result.output diff --git a/tests/integration/test_health_lint.py b/tests/integration/test_health_lint.py index ff81c9c..0f3d270 100644 --- a/tests/integration/test_health_lint.py +++ b/tests/integration/test_health_lint.py @@ -69,4 +69,4 @@ def test_lint_json_output(self, mock_run_lint, mock_connect, runner): ) assert result.exit_code == 0, result.output - assert '"issue_count": 0' in result.output \ No newline at end of file + assert '"issue_count": 0' in result.output diff --git a/tests/integration/test_health_nodes.py b/tests/integration/test_health_nodes.py index 40847ab..bc3d011 100644 --- a/tests/integration/test_health_nodes.py +++ b/tests/integration/test_health_nodes.py @@ -35,9 +35,7 @@ def test_nodes_table_jvm_fs(self, mock_manager_cls, mock_connect, runner): } } } - mock_manager.node_info.return_value = { - "nodes": {"n1": {"roles": ["data"]}} - } + mock_manager.node_info.return_value = {"nodes": {"n1": {"roles": ["data"]}}} result = runner.invoke( cli, @@ -73,4 +71,4 @@ def test_nodes_json_output(self, mock_manager_cls, mock_connect, runner): assert result.exit_code == 0, result.output payload = json.loads(result.output.strip()) - assert payload["node_count"] == 0 \ No newline at end of file + assert payload["node_count"] == 0 diff --git a/tests/integration/test_health_rollback.py b/tests/integration/test_health_rollback.py index 2716a0a..4a9a13b 100644 --- a/tests/integration/test_health_rollback.py +++ b/tests/integration/test_health_rollback.py @@ -79,4 +79,4 @@ def test_score_history_table(self, mock_query, mock_connect, runner): assert result.exit_code == 0, result.output assert "88/100" in result.output - assert "docker-cluster" in result.output \ No newline at end of file + assert "docker-cluster" in result.output diff --git a/tests/integration/test_health_shards.py b/tests/integration/test_health_shards.py index 9deba60..0eb2be8 100644 --- a/tests/integration/test_health_shards.py +++ b/tests/integration/test_health_shards.py @@ -38,7 +38,15 @@ def test_shards_analyze_table_output(self, mock_collect, mock_connect, runner): result = runner.invoke( cli, - ["-h", "http://localhost:9205", "-o", "table", "health", "shards", "--analyze"], + [ + "-h", + "http://localhost:9205", + "-o", + "table", + "health", + "shards", + "--analyze", + ], ) assert result.exit_code == 0, result.output @@ -117,4 +125,4 @@ def test_hotspots_table_output(self, mock_collect, mock_connect, runner): assert result.exit_code == 0, result.output assert "Node Hotspots" in result.output - assert "JVM heap" in result.output \ No newline at end of file + assert "JVM heap" in result.output diff --git a/tests/integration/test_health_trends.py b/tests/integration/test_health_trends.py index 494d346..d97a1ec 100644 --- a/tests/integration/test_health_trends.py +++ b/tests/integration/test_health_trends.py @@ -109,4 +109,4 @@ def test_trends_invalid_window_exits_2(self, runner, mock_client): ["health", "trends", "--window", "bad"], obj=mock_client, ) - assert result.exit_code == 2 \ No newline at end of file + assert result.exit_code == 2 diff --git a/tests/integration/test_index_fix.py b/tests/integration/test_index_fix.py index 4e3831b..efcd884 100644 --- a/tests/integration/test_index_fix.py +++ b/tests/integration/test_index_fix.py @@ -76,7 +76,9 @@ def test_fix_offers_three_remediation_modes( @patch("elastro.cli.cli.ElasticsearchClient.connect") @patch("elastro.health.remediation.diagnosis.list_unhealthy_indices") - def test_fix_reports_healthy_cluster(self, mock_list_unhealthy, mock_connect, runner): + def test_fix_reports_healthy_cluster( + self, mock_list_unhealthy, mock_connect, runner + ): mock_connect.return_value = None mock_list_unhealthy.return_value = [] @@ -177,7 +179,9 @@ def test_fix_shows_no_automated_fix_without_suggestion( @patch("elastro.cli.cli.ElasticsearchClient.connect") @patch("elastro.health.remediation.diagnosis.list_unhealthy_indices") - def test_fix_reports_operation_error(self, mock_list_unhealthy, mock_connect, runner): + def test_fix_reports_operation_error( + self, mock_list_unhealthy, mock_connect, runner + ): from elastro.core.errors import OperationError mock_connect.return_value = None @@ -186,4 +190,4 @@ def test_fix_reports_operation_error(self, mock_list_unhealthy, mock_connect, ru result = runner.invoke(cli, ["-h", "http://localhost:9205", "index", "fix"]) assert result.exit_code == 1, result.output - assert "Failed to list indices" in result.output \ No newline at end of file + assert "Failed to list indices" in result.output diff --git a/tests/unit/cli/test_deletion_dry_run.py b/tests/unit/cli/test_deletion_dry_run.py index 4a958e5..4f17e91 100644 --- a/tests/unit/cli/test_deletion_dry_run.py +++ b/tests/unit/cli/test_deletion_dry_run.py @@ -91,7 +91,16 @@ def test_index_delete_dry_run_json(self, mock_preview, mock_connect, runner=None result = runner.invoke( cli, - ["-h", "http://localhost:9205", "-o", "json", "index", "delete", "logs-2024", "--dry-run"], + [ + "-h", + "http://localhost:9205", + "-o", + "json", + "index", + "delete", + "logs-2024", + "--dry-run", + ], ) assert result.exit_code == 0, result.output @@ -137,4 +146,4 @@ def test_index_delete_dry_run_skips_confirm(self, mock_preview, mock_connect): assert result.exit_code == 0, result.output assert "Delete preview (dry-run)" in result.output - assert "DELETE /logs-2024" in result.output \ No newline at end of file + assert "DELETE /logs-2024" in result.output diff --git a/tests/unit/core/test_client.py b/tests/unit/core/test_client.py index 2e80847..9208b96 100644 --- a/tests/unit/core/test_client.py +++ b/tests/unit/core/test_client.py @@ -164,7 +164,6 @@ def test_connect_with_https(self): patch("elastro.core.client.Elasticsearch") as mock_es_class, patch("urllib3.disable_warnings") as mock_disable_warnings, ): - mock_instance = MagicMock() mock_instance.ping.return_value = True mock_es_class.return_value = mock_instance diff --git a/tests/unit/core/test_compliance_patterns.py b/tests/unit/core/test_compliance_patterns.py index 57bddb7..274e287 100644 --- a/tests/unit/core/test_compliance_patterns.py +++ b/tests/unit/core/test_compliance_patterns.py @@ -49,7 +49,9 @@ def test_url_https(self): assert PII_PATTERNS["url"].search("at https://secure.site.org/api/v1") def test_ipv6_full(self): - assert PII_PATTERNS["ipv6"].search("addr: 2001:0db8:85a3:0000:0000:8a2e:0370:7334") + assert PII_PATTERNS["ipv6"].search( + "addr: 2001:0db8:85a3:0000:0000:8a2e:0370:7334" + ) def test_vin_valid_17_chars(self): assert PII_PATTERNS["vin"].search("VIN: 1HGBH41JXMN109186") @@ -229,9 +231,7 @@ def test_mask_sensitive_fields_masks_mrn(self): def test_mask_sensitive_fields_masks_bank_account(self): chain = SanitizationChain(mask_sensitive_fields=True) - keep, doc = chain.sanitize( - {"bank_account": "123456789", "status": "active"} - ) + keep, doc = chain.sanitize({"bank_account": "123456789", "status": "active"}) assert doc["bank_account"] == "******" assert doc["status"] == "active" diff --git a/tests/unit/core/test_ingest_readers.py b/tests/unit/core/test_ingest_readers.py index 8293621..866d146 100644 --- a/tests/unit/core/test_ingest_readers.py +++ b/tests/unit/core/test_ingest_readers.py @@ -84,7 +84,7 @@ class TestNDJSONReader: def test_basic_ndjson(self, tmp_path: Path) -> None: ndjson_file = tmp_path / "test.ndjson" ndjson_file.write_text( - '{"name": "Alice", "age": 30}\n' '{"name": "Bob", "age": 25}\n' + '{"name": "Alice", "age": 30}\n{"name": "Bob", "age": 25}\n' ) docs = list(NDJSONReader(ndjson_file).read()) diff --git a/tests/unit/core/test_ingest_sql.py b/tests/unit/core/test_ingest_sql.py index a9438c9..d2ef6c8 100644 --- a/tests/unit/core/test_ingest_sql.py +++ b/tests/unit/core/test_ingest_sql.py @@ -52,7 +52,7 @@ def test_null_and_boolean(self, tmp_path: Path) -> None: """NULL and boolean literals should be coerced correctly.""" sql_file = tmp_path / "dump.sql" sql_file.write_text( - "INSERT INTO flags (id, active, notes) VALUES " "(1, TRUE, NULL);\n" + "INSERT INTO flags (id, active, notes) VALUES (1, TRUE, NULL);\n" ) docs = list(SQLDumpReader(sql_file).read()) @@ -82,7 +82,7 @@ def test_quoted_values_with_commas(self, tmp_path: Path) -> None: """Strings containing commas should be parsed correctly.""" sql_file = tmp_path / "dump.sql" sql_file.write_text( - "INSERT INTO addresses (id, addr) VALUES " "(1, '123 Main St, Suite 4');\n" + "INSERT INTO addresses (id, addr) VALUES (1, '123 Main St, Suite 4');\n" ) docs = list(SQLDumpReader(sql_file).read()) diff --git a/tests/unit/core/test_logger.py b/tests/unit/core/test_logger.py index b86e3d2..ae9168b 100644 --- a/tests/unit/core/test_logger.py +++ b/tests/unit/core/test_logger.py @@ -38,4 +38,4 @@ def test_child_logger_propagates_without_own_handlers(self): def test_get_logger_respects_level_override(self): logger_module.configure_logging("INFO") child = logger_module.get_logger("elastro.test.module", log_level="DEBUG") - assert child.level == logging.DEBUG \ No newline at end of file + assert child.level == logging.DEBUG diff --git a/tests/unit/health/test_assessor.py b/tests/unit/health/test_assessor.py index 89e0fd4..28d79bd 100644 --- a/tests/unit/health/test_assessor.py +++ b/tests/unit/health/test_assessor.py @@ -7,7 +7,11 @@ from elastro.core.client import ElasticsearchClient from elastro.health.assessor import HealthAssessor -from elastro.health.collectors.base import CollectContext, CollectorRegistry, CollectorResult +from elastro.health.collectors.base import ( + CollectContext, + CollectorRegistry, + CollectorResult, +) from elastro.health.collectors.cluster import ( ClusterHealthCollector, PendingTasksCollector, @@ -105,9 +109,7 @@ def collect(self, ctx: CollectContext) -> CollectorResult: } manager.pending_tasks.return_value = [] - report_out = HealthAssessor( - self.mock_client, registry=registry - ).run() + report_out = HealthAssessor(self.mock_client, registry=registry).run() self.assertEqual(report_out.overall_score, 100) self.assertEqual(len(report_out.findings), 1) @@ -203,4 +205,4 @@ def collect(self, ctx: CollectContext) -> CollectorResult: if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_audit.py b/tests/unit/health/test_audit.py index 9cc7ddf..48105c6 100644 --- a/tests/unit/health/test_audit.py +++ b/tests/unit/health/test_audit.py @@ -45,4 +45,4 @@ def test_log_fix_skips_index_on_dry_run(self): message="preview", ) logger.log_fix(result, session_id="sess-1") - client.client.index.assert_not_called() \ No newline at end of file + client.client.index.assert_not_called() diff --git a/tests/unit/health/test_collectors.py b/tests/unit/health/test_collectors.py index 284f873..e9a5475 100644 --- a/tests/unit/health/test_collectors.py +++ b/tests/unit/health/test_collectors.py @@ -72,4 +72,4 @@ def test_collect_success(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_common_issue_rules.py b/tests/unit/health/test_common_issue_rules.py index d292ba2..a64feee 100644 --- a/tests/unit/health/test_common_issue_rules.py +++ b/tests/unit/health/test_common_issue_rules.py @@ -123,4 +123,4 @@ def test_cluster_read_only_block(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_disk_blocks.py b/tests/unit/health/test_disk_blocks.py index b1f3faa..4cd93a2 100644 --- a/tests/unit/health/test_disk_blocks.py +++ b/tests/unit/health/test_disk_blocks.py @@ -33,4 +33,4 @@ def test_returns_blocked_indices(self): manager = IndexManager(client) blocked = discover_read_only_blocked_indices(manager) - assert blocked == ["logs-000001"] \ No newline at end of file + assert blocked == ["logs-000001"] diff --git a/tests/unit/health/test_disk_collector.py b/tests/unit/health/test_disk_collector.py index 15f42bf..5d3650d 100644 --- a/tests/unit/health/test_disk_collector.py +++ b/tests/unit/health/test_disk_collector.py @@ -40,9 +40,7 @@ def test_disk_used_percent(self): class TestDiskFindings(unittest.TestCase): def setUp(self): - payload = json.loads( - (FIXTURES / "nodes_stats_disk_pressure.json").read_text() - ) + payload = json.loads((FIXTURES / "nodes_stats_disk_pressure.json").read_text()) self.usages = build_node_disk_usages( { "node-1": { @@ -96,9 +94,7 @@ def test_collect_emits_findings(self, mock_manager_cls, mock_nodes_collect): "transient": {}, } - payload = json.loads( - (FIXTURES / "nodes_stats_disk_pressure.json").read_text() - ) + payload = json.loads((FIXTURES / "nodes_stats_disk_pressure.json").read_text()) mock_nodes_collect.return_value = Mock( status="ok", data={ @@ -124,4 +120,4 @@ def test_collect_emits_findings(self, mock_manager_cls, mock_nodes_collect): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_dry_run.py b/tests/unit/health/test_dry_run.py index 4ca8e2e..16deed6 100644 --- a/tests/unit/health/test_dry_run.py +++ b/tests/unit/health/test_dry_run.py @@ -13,7 +13,12 @@ summarize_fix_run, ) from elastro.health.remediation.executor import RemediationExecutor -from elastro.health.remediation.models import FixRunResult, IndexDiagnosis, PlannedAction, RemediationResult +from elastro.health.remediation.models import ( + FixRunResult, + IndexDiagnosis, + PlannedAction, + RemediationResult, +) from elastro.health.models import RemediationSafety @@ -143,4 +148,4 @@ def test_audit_skips_es_index_on_dry_run(self): ), session_id="sess-1", ) - client.client.index.assert_not_called() \ No newline at end of file + client.client.index.assert_not_called() diff --git a/tests/unit/health/test_exit_policy.py b/tests/unit/health/test_exit_policy.py index 63ae387..28d9719 100644 --- a/tests/unit/health/test_exit_policy.py +++ b/tests/unit/health/test_exit_policy.py @@ -179,4 +179,4 @@ def test_blocked_without_execution_returns_zero(self): ], ) def test_combine_exit_codes(codes, expected): - assert combine_exit_codes(*codes) == expected \ No newline at end of file + assert combine_exit_codes(*codes) == expected diff --git a/tests/unit/health/test_formatters.py b/tests/unit/health/test_formatters.py index 3cf4f94..3196d16 100644 --- a/tests/unit/health/test_formatters.py +++ b/tests/unit/health/test_formatters.py @@ -103,4 +103,4 @@ def test_render_assessment_detail_flag(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_health_report.py b/tests/unit/health/test_health_report.py index 9a9d015..12dc4fa 100644 --- a/tests/unit/health/test_health_report.py +++ b/tests/unit/health/test_health_report.py @@ -141,10 +141,8 @@ def test_feature_filter_passed_to_api(self): ctx.options["feature"] = "disk" result = self.collector.collect(ctx) self.assertEqual(result.status, "ok") - self.mock_es.health_report.assert_called_once_with( - feature="disk", verbose=True - ) + self.mock_es.health_report.assert_called_once_with(feature="disk", verbose=True) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_history.py b/tests/unit/health/test_history.py index 9dd4a18..59ee421 100644 --- a/tests/unit/health/test_history.py +++ b/tests/unit/health/test_history.py @@ -126,7 +126,9 @@ def test_query_assessment_history_missing_index_returns_empty(self): "index_not_found_exception: no such index [elastro-health-assessments]" ) - records = query_assessment_history(client, history_index="elastro-health-assessments") + records = query_assessment_history( + client, history_index="elastro-health-assessments" + ) assert records == [] @@ -153,4 +155,4 @@ def test_filter_records_by_window(self): filtered = filter_records_by_window(records, "7d") assert len(filtered) == 1 - assert filtered[0]["overall_score"] == 80 \ No newline at end of file + assert filtered[0]["overall_score"] == 80 diff --git a/tests/unit/health/test_hotspots_rule.py b/tests/unit/health/test_hotspots_rule.py index 552b533..f136df4 100644 --- a/tests/unit/health/test_hotspots_rule.py +++ b/tests/unit/health/test_hotspots_rule.py @@ -74,4 +74,4 @@ def test_finding_emitted_for_hotspot(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_ilm_collector.py b/tests/unit/health/test_ilm_collector.py index 3d51a2a..6ee393d 100644 --- a/tests/unit/health/test_ilm_collector.py +++ b/tests/unit/health/test_ilm_collector.py @@ -5,7 +5,11 @@ from elastro.core.client import ElasticsearchClient from elastro.health.collectors.base import CollectContext -from elastro.health.collectors.ilm import IlmCollector, _lifecycle_issue, _select_explain_targets +from elastro.health.collectors.ilm import ( + IlmCollector, + _lifecycle_issue, + _select_explain_targets, +) class TestIlmCollector(unittest.TestCase): @@ -50,4 +54,4 @@ def test_collect_returns_indices_and_findings( if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_ilm_status.py b/tests/unit/health/test_ilm_status.py index eb2ec82..3eef797 100644 --- a/tests/unit/health/test_ilm_status.py +++ b/tests/unit/health/test_ilm_status.py @@ -24,4 +24,4 @@ def test_returns_stuck_indices(self, mock_index_manager_cls, mock_ilm_manager_cl assert len(stuck) == 1 assert stuck[0].index_name == "logs-000001" - assert "snapshot failed" in stuck[0].issue \ No newline at end of file + assert "snapshot failed" in stuck[0].issue diff --git a/tests/unit/health/test_jvm_rule.py b/tests/unit/health/test_jvm_rule.py index c6b8fbe..caceb23 100644 --- a/tests/unit/health/test_jvm_rule.py +++ b/tests/unit/health/test_jvm_rule.py @@ -45,4 +45,4 @@ def test_no_finding_below_threshold(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_lint.py b/tests/unit/health/test_lint.py index 8212191..3446a4b 100644 --- a/tests/unit/health/test_lint.py +++ b/tests/unit/health/test_lint.py @@ -79,4 +79,4 @@ def test_unknown_category_raises(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_mapping_explosion_rule.py b/tests/unit/health/test_mapping_explosion_rule.py index 75b1c50..a88377e 100644 --- a/tests/unit/health/test_mapping_explosion_rule.py +++ b/tests/unit/health/test_mapping_explosion_rule.py @@ -45,4 +45,4 @@ def test_no_findings_below_threshold(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_mappings.py b/tests/unit/health/test_mappings.py index 7bc33d7..0bb4cbd 100644 --- a/tests/unit/health/test_mappings.py +++ b/tests/unit/health/test_mappings.py @@ -29,9 +29,7 @@ def test_count_mapping_fields_includes_nested_and_multi_fields(self): self.assertEqual(count_mapping_fields(properties), 5) def test_extract_field_limit_reads_index_setting(self): - limit = extract_field_limit( - {"index": {"mapping.total_fields.limit": "750"}} - ) + limit = extract_field_limit({"index": {"mapping.total_fields.limit": "750"}}) self.assertEqual(limit, 750) def test_summarize_index_mapping_computes_ratio(self): @@ -55,4 +53,4 @@ def test_summarize_index_mapping_computes_ratio(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_models.py b/tests/unit/health/test_models.py index 36a8a81..f7e89b7 100644 --- a/tests/unit/health/test_models.py +++ b/tests/unit/health/test_models.py @@ -50,4 +50,4 @@ def test_finding_serialization(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_nodes_collector.py b/tests/unit/health/test_nodes_collector.py index 477ac59..9c5d022 100644 --- a/tests/unit/health/test_nodes_collector.py +++ b/tests/unit/health/test_nodes_collector.py @@ -95,4 +95,4 @@ def test_table_includes_requested_metrics(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_oversharding_guide.py b/tests/unit/health/test_oversharding_guide.py index 5095312..5a246a7 100644 --- a/tests/unit/health/test_oversharding_guide.py +++ b/tests/unit/health/test_oversharding_guide.py @@ -78,4 +78,4 @@ def test_rule_emits_detail_and_metadata(self): assert "OVERSHARDED" in oversharded.summary assert oversharded.metadata.get("detail_sections") is not None assert oversharded.remediation is not None - assert "health shards --analyze" in oversharded.remediation.command \ No newline at end of file + assert "health shards --analyze" in oversharded.remediation.command diff --git a/tests/unit/health/test_oversharding_rule.py b/tests/unit/health/test_oversharding_rule.py index fc85484..c342e54 100644 --- a/tests/unit/health/test_oversharding_rule.py +++ b/tests/unit/health/test_oversharding_rule.py @@ -31,4 +31,4 @@ def test_no_findings_without_analysis(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_persistent_yellow_rule.py b/tests/unit/health/test_persistent_yellow_rule.py index a6750d1..2885850 100644 --- a/tests/unit/health/test_persistent_yellow_rule.py +++ b/tests/unit/health/test_persistent_yellow_rule.py @@ -54,4 +54,4 @@ def test_no_finding_without_enough_history(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_remediation_actions_ilm_disk.py b/tests/unit/health/test_remediation_actions_ilm_disk.py index f13cee8..c4e78d2 100644 --- a/tests/unit/health/test_remediation_actions_ilm_disk.py +++ b/tests/unit/health/test_remediation_actions_ilm_disk.py @@ -55,4 +55,4 @@ def test_clear_read_only_executes(self, mock_update): message = clear_read_only(manager, "logs-000001") mock_update.assert_called_once() - assert "logs-000001" in message \ No newline at end of file + assert "logs-000001" in message diff --git a/tests/unit/health/test_remediation_diagnosis.py b/tests/unit/health/test_remediation_diagnosis.py index 86b610c..a06f964 100644 --- a/tests/unit/health/test_remediation_diagnosis.py +++ b/tests/unit/health/test_remediation_diagnosis.py @@ -68,4 +68,4 @@ def test_returns_false_without_filter(self): {"deciders": [{"decider": "disk", "explanation": "not enough disk"}]} ] } - assert detect_routing_filter_fault(explain) is False \ No newline at end of file + assert detect_routing_filter_fault(explain) is False diff --git a/tests/unit/health/test_remediation_executor.py b/tests/unit/health/test_remediation_executor.py index 8122a95..b120c4d 100644 --- a/tests/unit/health/test_remediation_executor.py +++ b/tests/unit/health/test_remediation_executor.py @@ -30,9 +30,13 @@ def test_planned_reduce_replicas(self): def test_triggers_remediation_scan(self): assert RemediationCatalog.triggers_remediation_scan("elastro health fix") - assert RemediationCatalog.triggers_remediation_scan("elastro health assess --fix") + assert RemediationCatalog.triggers_remediation_scan( + "elastro health assess --fix" + ) assert RemediationCatalog.triggers_remediation_scan("elastro index fix") - assert RemediationCatalog.triggers_remediation_scan("elastro cluster allocation") + assert RemediationCatalog.triggers_remediation_scan( + "elastro cluster allocation" + ) assert not RemediationCatalog.triggers_remediation_scan("elastro health report") @@ -135,4 +139,4 @@ def test_rollback_restores_replica_count(self, client, tmp_path): result = executor.rollback(rollback_id, dry_run=False) assert result.success is True assert result.executed is True - assert "Restored settings" in result.message \ No newline at end of file + assert "Restored settings" in result.message diff --git a/tests/unit/health/test_remediation_planner.py b/tests/unit/health/test_remediation_planner.py index b87f432..3529c63 100644 --- a/tests/unit/health/test_remediation_planner.py +++ b/tests/unit/health/test_remediation_planner.py @@ -124,4 +124,4 @@ def test_plan_explicit_ilm_retry(self, mock_list_stuck): assert len(planned) == 1 assert planned[0].action_id == "ilm_retry" - assert "POST /logs-000042/_ilm/retry" == planned[0].planned_api_call \ No newline at end of file + assert "POST /logs-000042/_ilm/retry" == planned[0].planned_api_call diff --git a/tests/unit/health/test_remediation_safety.py b/tests/unit/health/test_remediation_safety.py index 81542cf..e3c56b6 100644 --- a/tests/unit/health/test_remediation_safety.py +++ b/tests/unit/health/test_remediation_safety.py @@ -112,4 +112,4 @@ def test_gate_rejects_typed_confirm_mismatch(self): ) decision = gate.decide(planned) assert decision.execute is False - assert "did not match" in (decision.message or "") \ No newline at end of file + assert "did not match" in (decision.message or "") diff --git a/tests/unit/health/test_replica_rule.py b/tests/unit/health/test_replica_rule.py index bd7cf54..1fe4d90 100644 --- a/tests/unit/health/test_replica_rule.py +++ b/tests/unit/health/test_replica_rule.py @@ -58,4 +58,4 @@ def test_skips_system_indices(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_rollback.py b/tests/unit/health/test_rollback.py index a2d1a50..b65a5c2 100644 --- a/tests/unit/health/test_rollback.py +++ b/tests/unit/health/test_rollback.py @@ -93,4 +93,4 @@ def test_apply_rollback_dry_run_skips_update(self): ) message = apply_rollback(manager, record, dry_run=True) manager.update.assert_not_called() - assert "Would restore settings" in message \ No newline at end of file + assert "Would restore settings" in message diff --git a/tests/unit/health/test_rule_engine.py b/tests/unit/health/test_rule_engine.py index 3f7be45..01de3d4 100644 --- a/tests/unit/health/test_rule_engine.py +++ b/tests/unit/health/test_rule_engine.py @@ -48,8 +48,7 @@ def ok_rule(_ctx: RuleContext): def test_default_rules_include_jvm_replica_and_persistent_yellow(self): rule_names = { - getattr(rule, "__name__", repr(rule)) - for rule in RuleEngine.default_rules() + getattr(rule, "__name__", repr(rule)) for rule in RuleEngine.default_rules() } self.assertIn("jvm_rule", rule_names) self.assertIn("replica_misconfig_findings", rule_names) @@ -63,4 +62,4 @@ def test_default_rules_include_jvm_replica_and_persistent_yellow(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_scoring.py b/tests/unit/health/test_scoring.py index 504f9a4..c65060f 100644 --- a/tests/unit/health/test_scoring.py +++ b/tests/unit/health/test_scoring.py @@ -24,4 +24,4 @@ def test_compute_weighted_score_empty(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_security_collector.py b/tests/unit/health/test_security_collector.py index d746185..d71fe1d 100644 --- a/tests/unit/health/test_security_collector.py +++ b/tests/unit/health/test_security_collector.py @@ -43,4 +43,4 @@ def test_skips_when_security_api_unavailable(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_shards_analysis.py b/tests/unit/health/test_shards_analysis.py index 4ae2a50..9544f75 100644 --- a/tests/unit/health/test_shards_analysis.py +++ b/tests/unit/health/test_shards_analysis.py @@ -42,4 +42,4 @@ def test_analyze_summary_shape(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_shards_collector.py b/tests/unit/health/test_shards_collector.py index 48b9403..344b696 100644 --- a/tests/unit/health/test_shards_collector.py +++ b/tests/unit/health/test_shards_collector.py @@ -40,4 +40,4 @@ def test_explain_allocation_for_index(self, mock_index_manager_cls): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_snapshots_collector.py b/tests/unit/health/test_snapshots_collector.py index 3cb0656..b257879 100644 --- a/tests/unit/health/test_snapshots_collector.py +++ b/tests/unit/health/test_snapshots_collector.py @@ -41,4 +41,4 @@ def test_list_error(self, mock_manager_cls): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/health/test_trends.py b/tests/unit/health/test_trends.py index 6e6b02f..f762223 100644 --- a/tests/unit/health/test_trends.py +++ b/tests/unit/health/test_trends.py @@ -96,11 +96,13 @@ def test_compute_trends_queries_history(self): ) assert report.sample_count == 2 assert report.cluster_name == "docker-cluster" - body = client.client.search.call_args.kwargs.get("body") or client.client.search.call_args[1].get("body") + body = client.client.search.call_args.kwargs.get( + "body" + ) or client.client.search.call_args[1].get("body") assert body["query"]["bool"]["filter"][0] == { "term": {"cluster_name": "docker-cluster"} } def test_parse_window_invalid_raises(self): with pytest.raises(ValueError): - parse_window("bad") \ No newline at end of file + parse_window("bad") diff --git a/tests/unit/server/test_cluster_inventory.py b/tests/unit/server/test_cluster_inventory.py index 13ce80c..6470473 100644 --- a/tests/unit/server/test_cluster_inventory.py +++ b/tests/unit/server/test_cluster_inventory.py @@ -42,6 +42,7 @@ def _mock_es(): es.snapshot.get_repository.return_value = { "repo1": {"type": "fs"}, } + def _cat_indices(**kwargs): if kwargs.get("index") == ".kibana*": return [{"index": ".kibana_1"}] @@ -74,4 +75,4 @@ def test_fetch_cluster_inventory_aggregates_metrics(self): assert inventory["documents"]["total"] == 1250000 assert inventory["storage"]["total_bytes"] == 5 * 1024**3 assert inventory["kibana"]["dashboards"] == 12 - assert inventory["backups"]["repository_count"] == 1 \ No newline at end of file + assert inventory["backups"]["repository_count"] == 1 diff --git a/tests/unit/server/test_gui_launch.py b/tests/unit/server/test_gui_launch.py index 0d39a74..fc88568 100644 --- a/tests/unit/server/test_gui_launch.py +++ b/tests/unit/server/test_gui_launch.py @@ -26,9 +26,12 @@ def test_should_not_reuse_when_health_api_missing(): "token": "abc", "version": __version__, } - with patch("elastro.server.os.kill") as mock_kill, patch( - "elastro.server._server_supports_health_api", - return_value=False, + with ( + patch("elastro.server.os.kill") as mock_kill, + patch( + "elastro.server._server_supports_health_api", + return_value=False, + ), ): mock_kill.return_value = None assert _should_reuse_gui_server(state) is False @@ -41,9 +44,12 @@ def test_should_reuse_compatible_server(): "token": "abc", "version": __version__, } - with patch("elastro.server.os.kill") as mock_kill, patch( - "elastro.server._server_supports_health_api", - return_value=True, + with ( + patch("elastro.server.os.kill") as mock_kill, + patch( + "elastro.server._server_supports_health_api", + return_value=True, + ), ): mock_kill.return_value = None - assert _should_reuse_gui_server(state) is True \ No newline at end of file + assert _should_reuse_gui_server(state) is True diff --git a/uv.lock b/uv.lock index faa69e1..2b252bf 100644 --- a/uv.lock +++ b/uv.lock @@ -33,7 +33,7 @@ resolution-markers = [ ] dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "idna", marker = "python_full_version < '3.10'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } @@ -50,7 +50,7 @@ resolution-markers = [ ] dependencies = [ { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "idna", version = "3.18", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } @@ -58,90 +58,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] -[[package]] -name = "black" -version = "25.11.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mypy-extensions", marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pathspec", marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytokens", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/d2/6caccbc96f9311e8ec3378c296d4f4809429c43a6cd2394e3c390e86816d/black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", size = 1743501, upload-time = "2025-11-10T01:59:06.202Z" }, - { url = "https://files.pythonhosted.org/packages/69/35/b986d57828b3f3dccbf922e2864223197ba32e74c5004264b1c62bc9f04d/black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", size = 1597308, upload-time = "2025-11-10T01:57:58.633Z" }, - { url = "https://files.pythonhosted.org/packages/39/8e/8b58ef4b37073f52b64a7b2dd8c9a96c84f45d6f47d878d0aa557e9a2d35/black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", size = 1656194, upload-time = "2025-11-10T01:57:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/8d/30/9c2267a7955ecc545306534ab88923769a979ac20a27cf618d370091e5dd/black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03", size = 1347996, upload-time = "2025-11-10T01:57:22.391Z" }, - { url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" }, - { url = "https://files.pythonhosted.org/packages/34/b9/f17dea34eecb7cc2609a89627d480fb6caea7b86190708eaa7eb15ed25e7/black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", size = 1352904, upload-time = "2025-11-10T01:59:26.252Z" }, - { url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" }, - { url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" }, - { url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" }, - { url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" }, - { url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" }, - { url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" }, - { url = "https://files.pythonhosted.org/packages/d5/9a/5b2c0e3215fe748fcf515c2dd34658973a1210bf610e24de5ba887e4f1c8/black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", size = 1743063, upload-time = "2025-11-10T02:02:43.175Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/245164c6efc27333409c62ba54dcbfbe866c6d1957c9a6c0647786e950da/black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", size = 1596867, upload-time = "2025-11-10T02:00:17.157Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6f/1a3859a7da205f3d50cf3a8bec6bdc551a91c33ae77a045bb24c1f46ab54/black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", size = 1655678, upload-time = "2025-11-10T01:57:09.028Z" }, - { url = "https://files.pythonhosted.org/packages/56/1a/6dec1aeb7be90753d4fcc273e69bc18bfd34b353223ed191da33f7519410/black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", size = 1347452, upload-time = "2025-11-10T01:57:01.871Z" }, - { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, -] - -[[package]] -name = "black" -version = "26.3.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -dependencies = [ - { name = "click", version = "8.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mypy-extensions", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pathspec", marker = "python_full_version >= '3.10'" }, - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pytokens", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" }, - { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" }, - { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" }, - { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" }, - { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, - { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, - { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, - { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, - { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, - { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, -] - [[package]] name = "certifi" version = "2026.2.25" @@ -415,7 +331,7 @@ wheels = [ [[package]] name = "elastro-client" -version = "1.3.53" +version = "1.13.0" source = { editable = "." } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -445,18 +361,21 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "black", version = "26.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "flake8" }, - { name = "isort", version = "6.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "isort", version = "8.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "httpx2", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "httpx2", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mypy", version = "1.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "mypy", version = "1.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-cov" }, + { name = "ruff" }, +] +ingest-sql = [ + { name = "sqlalchemy" }, ] test = [ + { name = "httpx2", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "httpx2", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-cov" }, @@ -464,13 +383,12 @@ test = [ [package.metadata] requires-dist = [ - { name = "black", marker = "extra == 'dev'", specifier = ">=22.0.0" }, { name = "click", specifier = ">=8.0.0" }, { name = "colorlog", specifier = ">=6.0.0" }, { name = "elasticsearch", specifier = ">=8.18.0,<9.0.0" }, { name = "fastapi", specifier = ">=0.111.0" }, - { name = "flake8", marker = "extra == 'dev'", specifier = ">=4.0.0" }, - { name = "isort", marker = "extra == 'dev'", specifier = ">=5.0.0" }, + { name = "httpx2", marker = "extra == 'dev'", specifier = ">=0.1.0" }, + { name = "httpx2", marker = "extra == 'test'", specifier = ">=0.1.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=0.9.0" }, { name = "pydantic", specifier = "==2.11.3" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, @@ -481,6 +399,8 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0" }, { name = "rich", specifier = ">=10.0.0" }, { name = "rich-click", specifier = ">=1.7.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" }, + { name = "sqlalchemy", marker = "extra == 'ingest-sql'", specifier = ">=2.0.0" }, { name = "tree-sitter", specifier = ">=0.23.0" }, { name = "tree-sitter-go", specifier = ">=0.23.0" }, { name = "tree-sitter-javascript", specifier = ">=0.23.0" }, @@ -488,7 +408,7 @@ requires-dist = [ { name = "tree-sitter-typescript", specifier = ">=0.23.0" }, { name = "uvicorn", specifier = ">=0.30.0" }, ] -provides-extras = ["test", "dev"] +provides-extras = ["test", "dev", "ingest-sql"] [[package]] name = "exceptiongroup" @@ -541,17 +461,91 @@ wheels = [ ] [[package]] -name = "flake8" -version = "7.3.0" +name = "greenlet" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, +resolution-markers = [ + "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b0/f5/3e9eafb4030588337b2a2ae4df46212956854e9069c07b53aa3caabafd47/greenlet-3.2.5.tar.gz", hash = "sha256:c816554eb33e7ecf9ba4defcb1fd8c994e59be6b4110da15480b3e7447ea4286", size = 191501, upload-time = "2026-02-20T20:08:51.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/d6/b3db928fc329b1b19ba32ffe143d2305f3aaafc583f5e1074c74ec445189/greenlet-3.2.5-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:34cc7cf8ab6f4b85298b01e13e881265ee7b3c1daf6bc10a2944abc15d4f87c3", size = 275803, upload-time = "2026-02-20T20:06:42.541Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/ab0ad4ff3d9e1faa266de4f6c79763b33fccd9265995f2940192494cc0ec/greenlet-3.2.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c11fe0cfb0ce33132f0b5d27eeadd1954976a82e5e9b60909ec2c4b884a55382", size = 633556, upload-time = "2026-02-20T20:30:41.594Z" }, + { url = "https://files.pythonhosted.org/packages/da/dd/7b3ac77099a1671af8077ecedb12c9a1be1310e4c35bb69fd34c18ab6093/greenlet-3.2.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a145f4b1c4ed7a2c94561b7f18b4beec3d3fb6f0580db22f7ed1d544e0620b34", size = 644943, upload-time = "2026-02-20T20:37:23.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/36/84630e9ff1dfc8b7690957c0f77834a84eabdbd9c4977c3a2d0cbd5325c2/greenlet-3.2.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1d01bdd67db3e5711e6246e451d7a0f75fae7bbf40adde129296a7f9aa7cc9", size = 639841, upload-time = "2026-02-20T20:07:17.473Z" }, + { url = "https://files.pythonhosted.org/packages/12/c4/6a2ee6c676dea7a05a3c3c1291fbc8ea44f26456b0accc891471293825af/greenlet-3.2.5-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd593db7ee1fa8a513a48a404f8cc4126998a48025e3f5cbbc68d51be0a6bf66", size = 588813, upload-time = "2026-02-20T20:07:56.171Z" }, + { url = "https://files.pythonhosted.org/packages/01/c0/75e75c2c993aa850292561ec80f5c263e3924e5843aa95a38716df69304c/greenlet-3.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac8db07bced2c39b987bba13a3195f8157b0cfbce54488f86919321444a1cc3c", size = 1117377, upload-time = "2026-02-20T20:32:48.452Z" }, + { url = "https://files.pythonhosted.org/packages/ee/03/e38ebf9024a0873fe8f60f5b7bc36bfb3be5e13efe4d798240f2d1f0fb73/greenlet-3.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4544ab2cfd5912e42458b13516429e029f87d8bbcdc8d5506db772941ae12493", size = 1141246, upload-time = "2026-02-20T20:06:23.576Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7b/c6e1192c795c0c12871e199237909a6bd35757d92c8472c7c019959b8637/greenlet-3.2.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acabf468466d18017e2ae5fbf1a5a88b86b48983e550e1ae1437b69a83d9f4ac", size = 276916, upload-time = "2026-02-20T20:06:18.166Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b6/9887b559f3e1952d23052ec352e9977e808a2246c7cb8282a38337221e88/greenlet-3.2.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:472841de62d60f2cafd60edd4fd4dd7253eb70e6eaf14b8990dcaf177f4af957", size = 636107, upload-time = "2026-02-20T20:30:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/8a/be/e3e48b63bbc27d660fa1d98aecb64906b90a12e686a436169c1330ef34b2/greenlet-3.2.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d951e7d628a6e8b68af469f0fe4f100ef64c4054abeb9cdafbfaa30a920c950", size = 648240, upload-time = "2026-02-20T20:37:24.608Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ac/e731ed62576e91e533b36d0d97325adc2786674ab9e48ed8a6a24f4ef4e9/greenlet-3.2.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8317d732e2ae0935d9ed2af2ea876fa714cf6f3b887a31ca150b54329b0a6e9", size = 643313, upload-time = "2026-02-20T20:07:19.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/64/99e5cdceb494bd4c1341c45b93f322601d2c8a5e1e4d1c7a2d24c5ed0570/greenlet-3.2.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce8aed6fdd5e07d3cbb988cbdc188266a4eb9e1a52db9ef5c6526e59962d3933", size = 591295, upload-time = "2026-02-20T20:07:57.286Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e9/968e11f388c2b8792d3b8b40a57984c894a3b4745dae3662dce722653bc5/greenlet-3.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:60c06b502d56d5451f60ca665691da29f79ed95e247bcf8ce5024d7bbe64acb9", size = 1120277, upload-time = "2026-02-20T20:32:50.103Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2c/b5f2c4c68d753dce08218dc5a6b21d82238fdfdc44309032f6fe24d285e6/greenlet-3.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d2a78e6f1bf3f1672df91e212a2f8314e1e7c922f065d14cbad4bc815059467", size = 1145746, upload-time = "2026-02-20T20:06:26.296Z" }, + { url = "https://files.pythonhosted.org/packages/ad/32/022b21523eee713e7550162d5ca6aed23f913cc2c6232b154b9fd9badc07/greenlet-3.2.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2acb30e77042f747ca81f0a10cc153296567e92e666c5e1b117f4595afd43352", size = 278412, upload-time = "2026-02-20T20:03:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/90/c5/8a3b0ed3cc34d8b988a44349437dfa0941f9c23ac108175f7b4ccea97111/greenlet-3.2.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:393c03c26c865f17f31d8db2f09603fadbe0581ad85a5d5908b131549fc38217", size = 644616, upload-time = "2026-02-20T20:30:44.823Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2c/2627bea183554695016af6cae93d7474fa90f61e5a6601a84ae7841cb720/greenlet-3.2.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:04e6a202cde56043fd355fefd1552c4caa5c087528121871d950eb4f1b51fa99", size = 658813, upload-time = "2026-02-20T20:37:26.255Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1b/75a5aeff487a26ba427a3837da6372f1fe6f2a9c6b2898e28ac99d491c11/greenlet-3.2.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45fcea7b697b91290b36eafc12fff479aca6ba6500d98ef6f34d5634c7119cbe", size = 655426, upload-time = "2026-02-20T20:07:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/53/91/9b5dfb4f3c88f8247c7a8f4c3759f0740bfa6bb0c59a9f6bf938e913df56/greenlet-3.2.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96e2bb8a56b7e1aed1dbfbbe0050cb2ecca99c7c91892fd1771e3afab63b3e3", size = 611138, upload-time = "2026-02-20T20:07:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8d/d0b086410512d9859c84e9242a9b341de9f5566011ddf3a3f6886b842b61/greenlet-3.2.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d7456e67b0be653dfe643bb37d9566cd30939c80f858e2ce6d2d54951f75b14a", size = 1126896, upload-time = "2026-02-20T20:32:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/ef/37/59fe12fe456e84ced6ba71781e28cde52a3124d1dd2077bc1727021f49fd/greenlet-3.2.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5ceb29d1f74c7280befbbfa27b9bf91ba4a07a1a00b2179a5d953fc219b16c42", size = 1154779, upload-time = "2026-02-20T20:06:27.583Z" }, + { url = "https://files.pythonhosted.org/packages/dd/95/d5d332fb73affaf7a1fbe80e49c2c7eae4f17c645af24a3b3fa25736d6f0/greenlet-3.2.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f2cc88b50b9006b324c1b9f5f3552f9d4564c78af57cdfb4c7baf4f0aa089146", size = 277166, upload-time = "2026-02-20T20:03:57.077Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/89458e20db5a4f1c64f9a0191561227e76d809941ca2d7529006d17d3450/greenlet-3.2.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e66872daffa360b2537170b73ad530f14fa31785b1bc78080125d92edf0a6def", size = 644674, upload-time = "2026-02-20T20:30:46.118Z" }, + { url = "https://files.pythonhosted.org/packages/90/f8/9962175d2f2eaa629a7fd7545abacc8c4deda3baa4e52c1526d2eb5f5546/greenlet-3.2.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c5445ddb7b586d870dad32ca9fc47c287d6022a528d194efdb8912093c5303ad", size = 658834, upload-time = "2026-02-20T20:37:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d7/826d0e080f0a7ad5ec47c8d143bbd3ca0887657bb806595fe2434d12938a/greenlet-3.2.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:752c896a8c976548faafe8a306d446c6a4c68d4fd24699b84d4393bd9ac69a8e", size = 655760, upload-time = "2026-02-20T20:07:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/41/cc/33bd4c2f816be8c8e16f71740c4130adf3a66a3dd2ba29de72b9d8dd1096/greenlet-3.2.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499b809e7738c8af0ff9ac9d5dd821cb93f4293065a9237543217f0b252f950a", size = 614132, upload-time = "2026-02-20T20:08:00.351Z" }, + { url = "https://files.pythonhosted.org/packages/48/79/f3891dcfc59097474a53cc3c624f2f2465e431ab493bda043b8c873fb20a/greenlet-3.2.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2c7429f6e9cea7cbf2637d86d3db12806ba970f7f972fcab39d6b54b4457cbaf", size = 1125286, upload-time = "2026-02-20T20:32:54.032Z" }, + { url = "https://files.pythonhosted.org/packages/ca/47/212b47e6d2d7a04c4083db1af2fdd291bc8fe99b7e3571bfa560b65fc361/greenlet-3.2.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a5e4b25e855800fba17713020c5c33e0a4b7a1829027719344f0c7c8870092a2", size = 1152825, upload-time = "2026-02-20T20:06:29Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8f/f880ff4587d236b4d06893fb34da6b299aa0d00f6c8259673f80e1b6d63c/greenlet-3.2.5-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:dbe0e81e24982bb45907ca20152b31c2e3300ca352fdc4acbd4956e4a2cbc195", size = 274946, upload-time = "2026-02-20T20:05:21.979Z" }, + { url = "https://files.pythonhosted.org/packages/3c/50/f6c78b8420187fdfe97fcf2e6d1dd243a7742d272c32fd4d4b1095474b37/greenlet-3.2.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15871afc0d78ec87d15d8412b337f287fc69f8f669346e391585824970931c48", size = 631781, upload-time = "2026-02-20T20:30:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/26/d6/3277f92e1961e6e9f41d9f173ea74b5c1f7065072637669f761626f26cc0/greenlet-3.2.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5bf0d7d62e356ef2e87e55e46a4e930ac165f9372760fb983b5631bb479e9d3a", size = 643740, upload-time = "2026-02-20T20:37:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6a/4f79d2e7b5ef3723fc5ffea0d6cb22627e5f95e0f19c973fa12bf1cf7891/greenlet-3.2.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6dff6433742073e5b6ad40953a78a0e8cddcb3f6869e5ea635d29a810ca5e7d0", size = 638382, upload-time = "2026-02-20T20:07:23.883Z" }, + { url = "https://files.pythonhosted.org/packages/4d/59/7aadf33f23c65dbf4db27e7f5b60c414797a61e954352ae4a86c5c8b0553/greenlet-3.2.5-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdd67619cefe1cc9fcab57c8853d2bb36eca9f166c0058cc0d428d471f7c785c", size = 587516, upload-time = "2026-02-20T20:08:02.841Z" }, + { url = "https://files.pythonhosted.org/packages/1d/46/b3422959f830de28a4eea447414e6bd7b980d755892f66ab52ad805da1c4/greenlet-3.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3828b309dfb1f117fe54867512a8265d8d4f00f8de6908eef9b885f4d8789062", size = 1115818, upload-time = "2026-02-20T20:32:55.786Z" }, + { url = "https://files.pythonhosted.org/packages/54/4a/3d1c9728f093415637cf3696909fa10852632e33e68238fb8ca60eb90de1/greenlet-3.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:67725ae9fea62c95cf1aa230f1b8d4dc38f7cd14f6103d1df8a5a95657eb8e54", size = 1140219, upload-time = "2026-02-20T20:06:30.334Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/21/117c8710abb7f146d804a124c07eb5964a60b90d02b72452885aecc18efa/greenlet-3.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7eacb17a9d41538a2bc4912eba5ef13823c83cb69e4d141d0813debe7163187f", size = 283510, upload-time = "2026-05-20T13:12:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f7/6762a56fa5f6c2295c449c6524e10ce481e381c994cc44d9d03aef0700fb/greenlet-3.5.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5cc9606aa5f4e0bde0d3bd502b44f743864c3ffa5cfa1011b1e30f5aa02366f", size = 599696, upload-time = "2026-05-20T14:00:02.906Z" }, + { url = "https://files.pythonhosted.org/packages/0f/05/85a511e68ee109aff0aa00b4b497806091dd2d82ce209e49c6e801bd5d92/greenlet-3.5.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3d35f87c7253b715d13d679e0783d845910144f282cb939fe1ba4ac8616269c", size = 612618, upload-time = "2026-05-20T14:05:39.202Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/8b83d18ae07c46c019617f35afd7b47aab7f9b4fbb12fc637d681e10bdd8/greenlet-3.5.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:540dae7b956209af4d70a3be35927b4055f617763771e5e84a5255bea934d2f5", size = 612947, upload-time = "2026-05-20T13:14:23.469Z" }, + { url = "https://files.pythonhosted.org/packages/5d/14/ad1f9fc9b82384c010212464a3702bd911f95dab2f1180bc6fbcfb1f958c/greenlet-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cdb691169715a9a492844a83246f090182247d1a5031dc78a403f68ba1e97", size = 1571425, upload-time = "2026-05-20T14:02:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/46/1c/43b8203cf10f4292c9e3d270e9e5f5ade79115a0a0ca5ea6f1be5f8915a7/greenlet-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d59e840387076a51016777a9328b3f2c427c6f9208a6e958bad251be50a648d", size = 1638688, upload-time = "2026-05-20T13:14:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/ac/6e/0344b1e99f58f71715456e46492101fd2daa408957b8186ade0a4b515da7/greenlet-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:b9152fca4a6466e114aaec745ae61cba739903a109754a9d4e1262f01e9259b1", size = 237763, upload-time = "2026-05-20T13:11:35.659Z" }, + { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, + { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, + { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a9/a3c2fa886c5b94863fb0e61b3bc14610b7aa94cf4f17f8741b11708305fc/greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", size = 234989, upload-time = "2026-05-20T13:08:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, + { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, ] [[package]] @@ -564,75 +558,120 @@ wheels = [ ] [[package]] -name = "idna" -version = "3.11" +name = "httpcore2" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "h11", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/18/e51f5729684acaaab4d1cfca0777ac0f86440c65d2814992442ed5ef02e1/httpcore2-2.0.0.tar.gz", hash = "sha256:403692e0a0e8ea6de90993cdea815b2454d2ff5426e61d2a244846c12506af76", size = 63864, upload-time = "2026-05-12T17:59:22.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/ae/67/784fd58a5b4dbe8b2aee0cafc6959cb8b53c1b7e9d6e4281f8a4c6935c10/httpcore2-2.0.0-py3-none-any.whl", hash = "sha256:4bd78675c48edafd522c5224a18e3a5f6e71dbe95a73497177d432072a7b6b43", size = 79673, upload-time = "2026-05-12T17:59:18.996Z" }, ] [[package]] -name = "importlib-metadata" -version = "8.7.1" +name = "httpcore2" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] dependencies = [ - { name = "zipp", marker = "python_full_version < '3.10'" }, + { name = "h11", marker = "python_full_version >= '3.10'" }, + { name = "truststore", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/9b/2b1d1833a58236d1f6ee755e027a3917da0db59cc9708554cefc440ee8b6/httpcore2-2.4.0.tar.gz", hash = "sha256:3093a8ab8980d9f910b9cb4351df9186a0ad2350a6284a9107ac9a362a584422", size = 64618, upload-time = "2026-06-11T06:35:53.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/4fdf2306143a92a471fad9f3655aa542d43aa9188a7c9534e82c9aecf837/httpcore2-2.4.0-py3-none-any.whl", hash = "sha256:5218779da5d6e3c2013ac706121abfb3815d450e0613495c0de50264dce58242", size = 80151, upload-time = "2026-06-11T06:35:50.89Z" }, ] [[package]] -name = "iniconfig" -version = "2.1.0" +name = "httpx2" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] -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" } +dependencies = [ + { name = "anyio", version = "4.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "httpcore2", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/95/868b3df4aa6f9cecb42aaf82a55a5a2c8ef40df76d709b812e1553d6a928/httpx2-2.0.0.tar.gz", hash = "sha256:f354249d2a9edce26e08fd2ad2276e98317b5763f673bb0d90f3ce9382cc2aed", size = 79506, upload-time = "2026-05-12T17:59:23.174Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/e9/25/66215a750faeb79b698286c0d0b94894482cfae10371ae4968ef6e165a28/httpx2-2.0.0-py3-none-any.whl", hash = "sha256:dfb5ae245f3985681296a000453f1e5fffee322eb531feabcdaf1557ad978b6a", size = 73424, upload-time = "2026-05-12T17:59:20.403Z" }, ] [[package]] -name = "iniconfig" -version = "2.3.0" +name = "httpx2" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +dependencies = [ + { name = "anyio", version = "4.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "httpcore2", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "idna", version = "3.18", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "truststore", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/60/b43ced4ccf26e95b396dbf67051d3e5042b645917d4da0469dd82a3bdd4f/httpx2-2.4.0.tar.gz", hash = "sha256:32e0734b61eb0824b3f56a9e98d6d92d381a3ef12c0045aa917ee63df6c411ef", size = 81691, upload-time = "2026-06-11T06:35:54.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/29/45/82bc57c3d9c3314f663b67cc057f1c017a6450685dde513f4f8db5cf431f/httpx2-2.4.0-py3-none-any.whl", hash = "sha256:425acd99297829599decf6701386dd84db3542597d36d3e2e4def930ecd57fd9", size = 74941, upload-time = "2026-06-11T06:35:52.235Z" }, ] [[package]] -name = "isort" -version = "6.1.0" +name = "idna" +version = "3.11" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] -dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/82/fa43935523efdfcce6abbae9da7f372b627b27142c3419fcf13bf5b0c397/isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481", size = 824325, upload-time = "2025-10-01T16:26:45.027Z" } + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/cc/9b681a170efab4868a032631dea1e8446d8ec718a7f657b94d49d1a12643/isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784", size = 94329, upload-time = "2025-10-01T16:26:43.291Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] -name = "isort" -version = "8.0.1" +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +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 = "iniconfig" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -736,15 +775,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] -[[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 = "mdurl" version = "0.1.2" @@ -877,30 +907,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] -[[package]] -name = "platformdirs" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.9.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -910,15 +916,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "pycodestyle" -version = "2.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, -] - [[package]] name = "pydantic" version = "2.11.3" @@ -1043,15 +1040,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/f2/1647933efaaad61846109a27619f3704929e758a09e6431b8f932a053d40/pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3", size = 2081073, upload-time = "2025-04-02T09:49:39.531Z" }, ] -[[package]] -name = "pyflakes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -1155,40 +1143,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] -[[package]] -name = "pytokens" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, - { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, - { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, - { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, - { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, - { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, - { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, - { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, - { url = "https://files.pythonhosted.org/packages/51/2a/f125667ce48105bf1f4e50e03cfa7b24b8c4f47684d7f1cf4dcb6f6b1c15/pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3", size = 161464, upload-time = "2026-01-30T01:03:39.11Z" }, - { url = "https://files.pythonhosted.org/packages/40/df/065a30790a7ca6bb48ad9018dd44668ed9135610ebf56a2a4cb8e513fd5c/pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1", size = 246159, upload-time = "2026-01-30T01:03:40.131Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1c/fd09976a7e04960dabc07ab0e0072c7813d566ec67d5490a4c600683c158/pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db", size = 259120, upload-time = "2026-01-30T01:03:41.233Z" }, - { url = "https://files.pythonhosted.org/packages/52/49/59fdc6fc5a390ae9f308eadeb97dfc70fc2d804ffc49dd39fc97604622ec/pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1", size = 262196, upload-time = "2026-01-30T01:03:42.696Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e7/d6734dccf0080e3dc00a55b0827ab5af30c886f8bc127bbc04bc3445daec/pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a", size = 103510, upload-time = "2026-01-30T01:03:43.915Z" }, - { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -1274,6 +1228,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl", hash = "sha256:2f99120fca78f536e07b114d3b60333bc4bb2a0969053b1250869bcdc1b5351b", size = 71491, upload-time = "2026-01-31T04:29:26.777Z" }, ] +[[package]] +name = "ruff" +version = "0.15.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, + { url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" }, + { url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, + { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, + { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1283,6 +1262,55 @@ 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 = "sqlalchemy" +version = "2.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", version = "3.2.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine == 'AMD64') or (python_full_version < '3.10' and platform_machine == 'WIN32') or (python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'ppc64le') or (python_full_version < '3.10' and platform_machine == 'win32') or (python_full_version < '3.10' and platform_machine == 'x86_64')" }, + { name = "greenlet", version = "3.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" }, + { url = "https://files.pythonhosted.org/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" }, + { url = "https://files.pythonhosted.org/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" }, + { url = "https://files.pythonhosted.org/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" }, + { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/34dcda476efe32dd18d473a1155983caaac8f4fa07bdfc952f5ba7df6c0c/sqlalchemy-2.0.51-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa18ae738b5170e253ad0bb6c4b0f07585081e8a6e50893e4d911d47b39a0904", size = 2165207, upload-time = "2026-06-15T16:08:42.233Z" }, + { url = "https://files.pythonhosted.org/packages/64/5c/e7c36b37f5a4ad17f92b87b7c464e4525c15a1d02201a4889b8f07f20454/sqlalchemy-2.0.51-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59cab3686b1bc039dd9cded2f8d0c08a246e84e76bd4ab5b4f18c7cdae293825", size = 3244038, upload-time = "2026-06-15T16:04:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e0/30d70e6e82025455c635016c052587c6a5924d35d9b08c48c53c57d2a56b/sqlalchemy-2.0.51-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111604e637da87031255ddc26c7d7bc22bc6af6f5d459ccff3af1b4660233a85", size = 3243600, upload-time = "2026-06-15T16:25:28.303Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b9/3464e5d68bdb56ac00ed2eb449f491bb2ef0d6139248dc90ce795191cd56/sqlalchemy-2.0.51-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad30ae663711786303fbcd46a47516302d201ee49a877cb3fac61f672895110a", size = 3194521, upload-time = "2026-06-15T16:04:49.667Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2d/9215ab552f16803d8b7434fdbb685d4a92971043db3041341cc81ef1e1b3/sqlalchemy-2.0.51-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b21f0e7efc7a5c509e953784e9d1575ebb8b4318960e7e7d7a93bb803626cf64", size = 3215721, upload-time = "2026-06-15T16:25:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5a/7d1cbc3b76303a4ae30f474dbd0ee57f47b096bd6ca74fdff01542b0f8de/sqlalchemy-2.0.51-cp39-cp39-win32.whl", hash = "sha256:a42ad6afcbaaa777241e347aa2e29155993045a0d6b7db74da61053ffe875fe0", size = 2123830, upload-time = "2026-06-15T16:09:00.074Z" }, + { url = "https://files.pythonhosted.org/packages/23/72/674fd7b2fa1e9d010c7adb0857c8004245e03177e3543db8089825b8bb6f/sqlalchemy-2.0.51-cp39-cp39-win_amd64.whl", hash = "sha256:2a97eaad21c84b4ef8010b11eeba9fe6153eb0b3df3ff8b6abc309df1b978ef7", size = 2147098, upload-time = "2026-06-15T16:09:01.366Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, +] + [[package]] name = "starlette" version = "0.49.3" @@ -1567,6 +1595,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052, upload-time = "2024-11-11T02:36:09.514Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -1630,12 +1667,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47 wheels = [ { url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -]