From 6062cb010eb99f9495fb24dc88a2fe1937f8797b Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 24 Aug 2026 13:32:20 +0200 Subject: [PATCH] Chiusura fase di ristrutturazione e modularizzazione del workflow per favorire sviluppo modulare --- harness/pyproject.toml | 3 ++ harness/tests/l0/test_db_connection.py | 8 ++-- harness/tests/l0/test_db_sampling.py | 2 +- harness/tests/l2/test_value_grounding_real.py | 2 +- harness/tests/memory/test_recall.py | 2 +- .../tests/memory/test_solved_finalization.py | 2 +- harness/tests/test_column_decisions.py | 6 ++- harness/tests/test_config_resources.py | 4 +- harness/tests/test_corpus_models.py | 5 ++- harness/tests/test_corpus_normalize.py | 2 +- harness/tests/test_corpus_pipeline.py | 37 +++++++++--------- harness/tests/test_corpus_publish.py | 9 +++-- harness/tests/test_cte_info.py | 14 +++---- harness/tests/test_cte_next.py | 2 +- harness/tests/test_cte_plan_doc.py | 2 +- harness/tests/test_ctetest.py | 12 +++--- harness/tests/test_decision_join_set_cli.py | 2 +- harness/tests/test_decisions_retract.py | 3 +- harness/tests/test_doctor_cli.py | 1 - harness/tests/test_dwh_adapters.py | 2 +- harness/tests/test_dwh_port_contract.py | 4 +- harness/tests/test_dwh_preprocess_job.py | 38 +++++++++++-------- .../tests/test_evidence_facade_contract.py | 6 +-- harness/tests/test_evidence_layout.py | 1 - harness/tests/test_evidence_port_contract.py | 8 +++- harness/tests/test_execute_inject_limit.py | 2 +- harness/tests/test_formula.py | 1 + harness/tests/test_http_evidence_source.py | 11 +++--- harness/tests/test_job_locking.py | 8 ++-- harness/tests/test_job_runner.py | 3 +- harness/tests/test_lsh_job_resume.py | 8 +--- harness/tests/test_manifest_pi_fields.py | 2 +- harness/tests/test_memory_metadata.py | 14 +++---- harness/tests/test_memory_promotion.py | 16 ++++---- harness/tests/test_module_boundaries.py | 1 - harness/tests/test_mschema_eligibility.py | 10 ++--- harness/tests/test_mschema_render.py | 4 +- harness/tests/test_ollama_ensure.py | 2 +- harness/tests/test_phase_effective.py | 2 +- harness/tests/test_phase_reopen_order.py | 2 +- harness/tests/test_pi_skill_projection.py | 1 - harness/tests/test_promoted_columns_for.py | 3 +- .../tests/test_registry_evidence_config.py | 4 +- harness/tests/test_s3_evidence_source.py | 4 +- harness/tests/test_schema_columns_cmd.py | 6 +-- harness/tests/test_schema_introspect_guard.py | 12 +++--- harness/tests/test_session_documents.py | 17 ++++++--- harness/tests/test_session_list_json.py | 4 +- harness/tests/test_session_mutations.py | 4 +- harness/tests/test_session_name.py | 8 ++-- harness/tests/test_session_repository.py | 5 +-- .../tests/test_session_repository_workflow.py | 2 +- harness/tests/test_set_schema_linking.py | 2 +- harness/tests/test_set_schema_linking_cli.py | 2 +- harness/tests/test_sql_preview_json.py | 1 - harness/tests/test_sqlcheck.py | 4 +- harness/tests/test_sync_schema_linking.py | 4 +- harness/tests/test_value_grounding.py | 3 +- .../test_workflow_observable_contract.py | 6 +-- harness/tht/adapters/dwh/postgres.py | 2 +- harness/tht/adapters/dwh/thoth_rest.py | 2 +- harness/tht/cli/db_cmd.py | 1 + harness/tht/cli/lsh_cmd.py | 2 +- harness/tht/cli/memory_cmd.py | 2 +- harness/tht/cli/phase_cmd.py | 15 ++++---- harness/tht/cli/preprocess_cmd.py | 4 +- harness/tht/cli/schema_cmd.py | 10 ++--- harness/tht/cli/search_cmd.py | 2 +- harness/tht/cli/session_cmd.py | 6 +-- harness/tht/db/execute.py | 4 +- harness/tht/db/fetch_ca.py | 8 ++-- harness/tht/db/sampling.py | 6 +-- harness/tht/evidence/__init__.py | 7 ++-- harness/tht/evidence/adapters/s3.py | 5 ++- harness/tht/evidence/corpus/models.py | 1 - harness/tht/evidence/corpus/normalize.py | 3 +- harness/tht/evidence/corpus/pipeline.py | 24 ++++++------ harness/tht/evidence/corpus/store.py | 11 +++--- harness/tht/evidence/formula_store.py | 4 +- harness/tht/evidence/preprocessing.py | 2 +- harness/tht/jobs/locking.py | 5 ++- harness/tht/jobs/models.py | 16 +++++--- harness/tht/jobs/runner.py | 8 ++-- harness/tht/memory/solved.py | 2 +- harness/tht/mschema/render.py | 4 +- harness/tht/pi_skill_projection.py | 3 +- harness/tht/ports/__init__.py | 8 ++-- harness/tht/rest/client.py | 2 +- harness/tht/session/filesystem_repository.py | 4 +- harness/tht/session/models.py | 2 +- harness/tht/session/postgres_repository.py | 4 +- harness/tht/session/repository.py | 3 +- harness/tht/session/store.py | 4 +- harness/tht/taskdoc.py | 2 +- harness/tht/vendor/thoth_lsh.py | 13 ++++--- harness/tht/workflow.py | 7 ++-- 96 files changed, 304 insertions(+), 259 deletions(-) diff --git a/harness/pyproject.toml b/harness/pyproject.toml index 05fbddc3..b2786b23 100644 --- a/harness/pyproject.toml +++ b/harness/pyproject.toml @@ -42,6 +42,9 @@ line-length = 100 [tool.ruff.lint.per-file-ignores] "tht/cli/__init__.py" = ["E402"] +[tool.ruff.lint.flake8-bugbear] +extend-immutable-calls = ["typer.Argument", "typer.Option"] + [tool.pytest.ini_options] testpaths = ["tests"] markers = [ diff --git a/harness/tests/l0/test_db_connection.py b/harness/tests/l0/test_db_connection.py index b8d58c61..f101473a 100644 --- a/harness/tests/l0/test_db_connection.py +++ b/harness/tests/l0/test_db_connection.py @@ -6,6 +6,7 @@ is not assumed reliable' gains real teeth for the data layer. """ import pytest from sqlalchemy import create_engine, text +from sqlalchemy.exc import SQLAlchemyError from tht.db.connection import can_create_in_schema, ping, writable_tables @@ -43,10 +44,9 @@ def test_read_only_role_cannot_insert(ro_url): by our engine).""" engine = create_engine(ro_url) try: - with pytest.raises(Exception): - with engine.begin() as conn: - conn.execute(text('INSERT INTO dw.dim_pazienti VALUES (999, %s, %s)'), - ("test", "test")) + with pytest.raises(SQLAlchemyError), engine.begin() as conn: + conn.execute(text('INSERT INTO dw.dim_pazienti VALUES (999, %s, %s)'), + ("test", "test")) finally: engine.dispose() diff --git a/harness/tests/l0/test_db_sampling.py b/harness/tests/l0/test_db_sampling.py index e15529d4..0d8a220f 100644 --- a/harness/tests/l0/test_db_sampling.py +++ b/harness/tests/l0/test_db_sampling.py @@ -40,7 +40,7 @@ def test_unique_values_for_lsh_returns_most_frequent(admin_engine): schema = introspect(admin_engine, "testdb", "dw") # Before classify_all, all text columns are eligible=True by default. Sampling # only touches text types regardless. - values, skipped, truncated = unique_values_for_lsh( + values, _skipped, _truncated = unique_values_for_lsh( admin_engine, schema, LshConfig(max_values_per_column=100) ) # dim_pazienti.citta: Milano, Bergamo, Brescia (3 distinct, all eligible text) diff --git a/harness/tests/l2/test_value_grounding_real.py b/harness/tests/l2/test_value_grounding_real.py index 5bf8231c..629c3f32 100644 --- a/harness/tests/l2/test_value_grounding_real.py +++ b/harness/tests/l2/test_value_grounding_real.py @@ -38,7 +38,7 @@ def test_ablazione_returns_multiple_columns(l2_env): schema_name = ws.database.db_schema try: lsh, minhashes, meta = load_index(index_dir, schema_name) - except Exception as e: + except Exception as e: # noqa: BLE001 - any unusable external index skips this L2 probe pytest.skip(f"LSH index not built yet (run tht preprocess dwh --steps lsh -c {WORKSPACE}): {e}") hits = query_index(lsh, minhashes, "ablazione", meta, top_n=20) diff --git a/harness/tests/memory/test_recall.py b/harness/tests/memory/test_recall.py index bed1cc0d..58757e0b 100644 --- a/harness/tests/memory/test_recall.py +++ b/harness/tests/memory/test_recall.py @@ -1,7 +1,7 @@ import json +import uuid from datetime import UTC, datetime from types import SimpleNamespace -import uuid from typer.testing import CliRunner diff --git a/harness/tests/memory/test_solved_finalization.py b/harness/tests/memory/test_solved_finalization.py index 6c3d00eb..08a67e0d 100644 --- a/harness/tests/memory/test_solved_finalization.py +++ b/harness/tests/memory/test_solved_finalization.py @@ -1,7 +1,7 @@ +import uuid from datetime import UTC, datetime from pathlib import Path from types import SimpleNamespace -import uuid from tht.cli import session_cmd from tht.decisions import DecisionInput diff --git a/harness/tests/test_column_decisions.py b/harness/tests/test_column_decisions.py index 9539e26b..a060096e 100644 --- a/harness/tests/test_column_decisions.py +++ b/harness/tests/test_column_decisions.py @@ -1,6 +1,7 @@ -from tht.decisions import DecisionType import typing +from tht.decisions import DecisionType + def test_column_decision_types_exist(): allowed = set(typing.get_args(DecisionType)) @@ -9,8 +10,9 @@ def test_column_decision_types_exist(): def test_f4_emits_column_types(): - import yaml from pathlib import Path + + import yaml wf = yaml.safe_load(Path("workflow.yaml").read_text()) f4 = next(p for p in wf["phases"] if p["id"] == "F4") assert "column_promoted" in f4["emits"] diff --git a/harness/tests/test_config_resources.py b/harness/tests/test_config_resources.py index ad72015c..c9249d1f 100644 --- a/harness/tests/test_config_resources.py +++ b/harness/tests/test_config_resources.py @@ -1,7 +1,5 @@ import pytest -from tht.evidence.adapters import FilesystemEvidenceSource, HttpManifestEvidenceSource -from tht.evidence import build_sources from tht.config import ( ConfigError, PgvectorDirectConfig, @@ -12,6 +10,8 @@ from tht.config import ( load_config, workspace_id_for_config, ) +from tht.evidence import build_sources +from tht.evidence.adapters import FilesystemEvidenceSource, HttpManifestEvidenceSource def test_direct_vector_passwords_load_from_file_references(monkeypatch, tmp_path): diff --git a/harness/tests/test_corpus_models.py b/harness/tests/test_corpus_models.py index a2c053bb..3253f882 100644 --- a/harness/tests/test_corpus_models.py +++ b/harness/tests/test_corpus_models.py @@ -209,7 +209,10 @@ def test_model_copy_revalidates_records_and_manifests(): def test_manifest_datetimes_are_aware_and_normalized_to_utc(): with pytest.raises(ValidationError, match="timezone-aware"): - CorpusManifest(created_at=datetime(2026, 7, 12), pipeline_version="evidence-v1") + CorpusManifest( + created_at=datetime(2026, 7, 12), # noqa: DTZ001 - verifies rejection + pipeline_version="evidence-v1", + ) plus_two = datetime(2026, 7, 12, 12, tzinfo=timezone(timedelta(hours=2))) manifest = CorpusManifest(created_at=plus_two, pipeline_version="evidence-v1") diff --git a/harness/tests/test_corpus_normalize.py b/harness/tests/test_corpus_normalize.py index 3b2165d1..96d27c53 100644 --- a/harness/tests/test_corpus_normalize.py +++ b/harness/tests/test_corpus_normalize.py @@ -3,8 +3,8 @@ from datetime import UTC, datetime import pytest -from tht.evidence.corpus.normalize import MAX_DOCUMENT_BYTES, PermanentNormalizationError, normalize from tht.evidence.contracts import AcquiredDocument, SourceObject +from tht.evidence.corpus.normalize import MAX_DOCUMENT_BYTES, PermanentNormalizationError, normalize def acquired(content: bytes, *, media_type: str = "text/markdown") -> AcquiredDocument: diff --git a/harness/tests/test_corpus_pipeline.py b/harness/tests/test_corpus_pipeline.py index 5edd3891..0940fccd 100644 --- a/harness/tests/test_corpus_pipeline.py +++ b/harness/tests/test_corpus_pipeline.py @@ -2,11 +2,11 @@ from datetime import UTC, datetime, timedelta import pytest +from tht.evidence.contracts import AcquiredDocument, SourceObject from tht.evidence.corpus.chunk import ChunkPolicy +from tht.evidence.corpus.models import CanonicalChunk, CanonicalDocument, CorpusManifest from tht.evidence.corpus.pipeline import CorpusPipeline, PipelineError, PipelineResult from tht.evidence.corpus.store import CorpusStore -from tht.evidence.corpus.models import CanonicalChunk, CanonicalDocument, CorpusManifest -from tht.evidence.contracts import AcquiredDocument, SourceObject from tht.ports.vector import VectorCapabilities, VectorHealth @@ -273,6 +273,7 @@ def test_gc_preserves_vector_dependencies_of_retained_manifests(tmp_path): def test_active_searcher_without_active_fails_closed_for_evidence(tmp_path): from types import SimpleNamespace + from tht.evidence.search import active_searcher class Delegate: @@ -287,6 +288,7 @@ def test_active_searcher_without_active_fails_closed_for_evidence(tmp_path): def test_active_searcher_splits_default_and_mixed_kinds_before_global_limit(tmp_path): from types import SimpleNamespace + from tht.evidence.search import ActiveEvidenceSearcher store = CorpusStore(tmp_path / "corpus") @@ -319,6 +321,7 @@ def test_active_searcher_splits_default_and_mixed_kinds_before_global_limit(tmp_ def test_active_evidence_query_holds_lock_against_publish(tmp_path): import threading from types import SimpleNamespace + from tht.evidence.search import ActiveEvidenceSearcher first_pipeline = pipeline(tmp_path, Source([(item("one", "a"), "old")]), vectors=Vectors()) @@ -519,9 +522,9 @@ def test_unchanged_job_reuses_active_generation_without_new_directory(tmp_path): "hello", )]) candidate = pipeline(tmp_path, source) - args = dict(workspace_id="demo", workspace_root=tmp_path, - config_fingerprint="sha256:" + "1" * 64, - input_fingerprint="sha256:" + "2" * 64) + args = {"workspace_id": "demo", "workspace_root": tmp_path, + "config_fingerprint": "sha256:" + "1" * 64, + "input_fingerprint": "sha256:" + "2" * 64} first = candidate.run_as_job(**args) snapshot = first.manifest.metadata["source_snapshot"]["fs:one"] assert snapshot == { @@ -546,9 +549,9 @@ def test_job_source_snapshot_change_forces_publish_with_same_fingerprint(tmp_pat metadata={"media_type": "text/markdown", "size": 5, "label": "original"}, ) vectors = Vectors() - args = dict(workspace_id="demo", workspace_root=tmp_path, - config_fingerprint="sha256:" + "1" * 64, - input_fingerprint="sha256:" + "2" * 64) + args = {"workspace_id": "demo", "workspace_root": tmp_path, + "config_fingerprint": "sha256:" + "1" * 64, + "input_fingerprint": "sha256:" + "2" * 64} first = pipeline(tmp_path, Source([(original, "hello")]), vectors=vectors).run_as_job(**args) updates = { "uri": "file:///safe/renamed.md", @@ -567,9 +570,9 @@ def test_job_source_snapshot_change_forces_publish_with_same_fingerprint(tmp_pat def test_job_binding_change_forces_publish(tmp_path, fingerprint_name): vectors = Vectors() source_object = item("one", "a") - args = dict(workspace_id="demo", workspace_root=tmp_path, - config_fingerprint="sha256:" + "1" * 64, - input_fingerprint="sha256:" + "2" * 64) + args = {"workspace_id": "demo", "workspace_root": tmp_path, + "config_fingerprint": "sha256:" + "1" * 64, + "input_fingerprint": "sha256:" + "2" * 64} first = pipeline(tmp_path, Source([(source_object, "hello")]), vectors=vectors).run_as_job(**args) args[fingerprint_name] = "sha256:" + "3" * 64 source = Source([(source_object, "hello")]) @@ -587,9 +590,9 @@ def test_job_incomplete_active_contract_never_noops(tmp_path, damage): vectors = Vectors() source_object = item("one", "a") - args = dict(workspace_id="demo", workspace_root=tmp_path, - config_fingerprint="sha256:" + "1" * 64, - input_fingerprint="sha256:" + "2" * 64) + args = {"workspace_id": "demo", "workspace_root": tmp_path, + "config_fingerprint": "sha256:" + "1" * 64, + "input_fingerprint": "sha256:" + "2" * 64} candidate = pipeline(tmp_path, Source([(source_object, "hello")]), vectors=vectors) first = candidate.run_as_job(**args) if damage == "legacy_metadata": @@ -628,9 +631,9 @@ def test_job_corrupt_canonical_document_or_chunk_never_noops(tmp_path, damage): modified_at=datetime(2026, 1, 1, tzinfo=UTC), metadata={"media_type": "text/markdown", "size": 11, "owner": "docs"}, ) - args = dict(workspace_id="demo", workspace_root=tmp_path, - config_fingerprint="sha256:" + "1" * 64, - input_fingerprint="sha256:" + "2" * 64) + args = {"workspace_id": "demo", "workspace_root": tmp_path, + "config_fingerprint": "sha256:" + "1" * 64, + "input_fingerprint": "sha256:" + "2" * 64} candidate = pipeline( tmp_path, Source([(source_object, "hello world")]), vectors=vectors, policy=ChunkPolicy(version="chunk-v1", max_chars=6), diff --git a/harness/tests/test_corpus_publish.py b/harness/tests/test_corpus_publish.py index 3e8fca75..38b7dc4b 100644 --- a/harness/tests/test_corpus_publish.py +++ b/harness/tests/test_corpus_publish.py @@ -1,6 +1,7 @@ -import pytest import os +import pytest + from tht.evidence.corpus.models import CorpusManifest from tht.evidence.corpus.store import CorpusStore, UnsafeCorpusPath @@ -111,9 +112,10 @@ def test_published_inventory_excludes_staged_and_invalid_newer_directories(tmp_p def test_owned_copy_uses_validated_descriptor_bytes_when_source_is_replaced(tmp_path, monkeypatch): - from tht.evidence.corpus.models import CanonicalDocument import hashlib + from tht.evidence.corpus.models import CanonicalDocument + content = "active bytes" document = CanonicalDocument( document_id="doc:" + "c" * 64, source_id="fs:copy", source_uri="file:///copy", @@ -140,9 +142,10 @@ def test_owned_copy_uses_validated_descriptor_bytes_when_source_is_replaced(tmp_ def test_materialized_snapshot_uses_identified_manifest_when_active_changes(tmp_path): - from tht.evidence.corpus.models import CanonicalDocument import hashlib + from tht.evidence.corpus.models import CanonicalDocument + def doc(content, fingerprint): return CanonicalDocument( document_id="doc:" + hashlib.sha256(content.encode()).hexdigest(), diff --git a/harness/tests/test_cte_info.py b/harness/tests/test_cte_info.py index 1eae2575..cb9ce267 100644 --- a/harness/tests/test_cte_info.py +++ b/harness/tests/test_cte_info.py @@ -6,7 +6,7 @@ cte_plan_doc.json entry, and the last CteTestRecord for the CTE. --json output m pristine (only valid JSON on stdout). """ import json -from datetime import datetime +from datetime import UTC, datetime from typer.testing import CliRunner @@ -18,7 +18,7 @@ from tht.session.store import create_session def _db(): - return DatabaseConfig(database="testdb", user="u", password="p", **{"schema": "public"}) # noqa: S106 + return DatabaseConfig(database="testdb", user="u", password="p", schema="public") def _patch_cfg(monkeypatch, tmp_path): @@ -46,7 +46,7 @@ def test_info_happy_path_json(tmp_path, monkeypatch): sid, sdir = _make_session(tmp_path, ["a", "b"]) (sdir / "ctes" / "b.sql").write_text("WITH b AS (SELECT 1)") append_cte_test(sdir, CteTestRecord( - name="b", ts=datetime(2025, 1, 1, 12, 0), sql_hash="h1", status="ok", + name="b", ts=datetime(2025, 1, 1, 12, 0, tzinfo=UTC), sql_hash="h1", status="ok", columns=["x"], row_sample=1, execution_ms=5, preview_rows=[[1]], )) _patch_cfg(monkeypatch, tmp_path) @@ -105,10 +105,10 @@ def test_info_last_test_is_most_recent_record(tmp_path, monkeypatch): sid, sdir = _make_session(tmp_path, ["a"]) (sdir / "ctes" / "a.sql").write_text("WITH a AS (SELECT 1)") append_cte_test(sdir, CteTestRecord( - name="a", ts=datetime(2025, 1, 1), sql_hash="old", status="ok", + name="a", ts=datetime(2025, 1, 1, tzinfo=UTC), sql_hash="old", status="ok", )) append_cte_test(sdir, CteTestRecord( - name="a", ts=datetime(2025, 1, 2), sql_hash="new", status="ok", + name="a", ts=datetime(2025, 1, 2, tzinfo=UTC), sql_hash="new", status="ok", )) _patch_cfg(monkeypatch, tmp_path) @@ -134,7 +134,7 @@ def test_info_missing_plan_exit_1(tmp_path, monkeypatch): def test_info_name_not_in_plan_exit_1(tmp_path, monkeypatch): - sid, sdir = _make_session(tmp_path, ["a"]) + sid, _sdir = _make_session(tmp_path, ["a"]) _patch_cfg(monkeypatch, tmp_path) res = CliRunner().invoke(cte_app, ["info", "zzz", "--session", sid, "--json"]) assert res.exit_code == 1 @@ -142,7 +142,7 @@ def test_info_name_not_in_plan_exit_1(tmp_path, monkeypatch): def test_info_missing_sql_file_exit_1(tmp_path, monkeypatch): - sid, sdir = _make_session(tmp_path, ["a"]) + sid, _sdir = _make_session(tmp_path, ["a"]) _patch_cfg(monkeypatch, tmp_path) res = CliRunner().invoke(cte_app, ["info", "a", "--session", sid, "--json"]) assert res.exit_code == 1 diff --git a/harness/tests/test_cte_next.py b/harness/tests/test_cte_next.py index f734524c..5ec65ee2 100644 --- a/harness/tests/test_cte_next.py +++ b/harness/tests/test_cte_next.py @@ -10,7 +10,7 @@ from tht.session.store import create_session def _db(): - return DatabaseConfig(database="testdb", user="u", password="p", **{"schema": "public"}) # noqa: S106 + return DatabaseConfig(database="testdb", user="u", password="p", schema="public") def _patch_cfg(monkeypatch, tmp_path): diff --git a/harness/tests/test_cte_plan_doc.py b/harness/tests/test_cte_plan_doc.py index 2d6a397c..4ba4ca6a 100644 --- a/harness/tests/test_cte_plan_doc.py +++ b/harness/tests/test_cte_plan_doc.py @@ -17,7 +17,7 @@ from tht.session.store import create_session def _db(): - return DatabaseConfig(database="testdb", user="u", password="p", **{"schema": "public"}) # noqa: S106 + return DatabaseConfig(database="testdb", user="u", password="p", schema="public") def _patch_cfg(monkeypatch, tmp_path): diff --git a/harness/tests/test_ctetest.py b/harness/tests/test_ctetest.py index 890e7841..6992c8ee 100644 --- a/harness/tests/test_ctetest.py +++ b/harness/tests/test_ctetest.py @@ -7,7 +7,7 @@ and the ledger I/O (load/append, tolerant of JSON-array and JSONL formats). Pure logic, no DB. """ import json -from datetime import date, datetime +from datetime import UTC, date, datetime from decimal import Decimal import pytest @@ -82,10 +82,10 @@ def test_build_test_sql_single_cte(): # --- ledger I/O: load_cte_tests / append_cte_test --------------------------- def _record(**kw) -> CteTestRecord: - base = dict( - name="ablazione_q", ts=datetime(2025, 1, 1, 12, 0), sql_hash="abc123", - status="ok", columns=["x"], row_sample=5, execution_ms=42, - ) + base = { + "name": "ablazione_q", "ts": datetime(2025, 1, 1, 12, 0, tzinfo=UTC), "sql_hash": "abc123", + "status": "ok", "columns": ["x"], "row_sample": 5, "execution_ms": 42, + } base.update(kw) return CteTestRecord(**base) @@ -147,7 +147,7 @@ def test_jsonable_passes_through_native_types(): def test_jsonable_coerces_decimal_and_date_to_str(): assert _jsonable(Decimal("12.34")) == "12.34" assert _jsonable(date(2025, 1, 1)) == "2025-01-01" - assert _jsonable(datetime(2025, 1, 1, 12, 0, 0)) == "2025-01-01 12:00:00" + assert _jsonable(datetime(2025, 1, 1, 12, 0, 0)) == "2025-01-01 12:00:00" # noqa: DTZ001 def test_jsonable_truncates_long_strings(): diff --git a/harness/tests/test_decision_join_set_cli.py b/harness/tests/test_decision_join_set_cli.py index f14807d7..94bc1cee 100644 --- a/harness/tests/test_decision_join_set_cli.py +++ b/harness/tests/test_decision_join_set_cli.py @@ -17,7 +17,7 @@ def _concurrent_append_worker(session, subject, start, ready, done): try: append_decision(session, type="concept_clarified", subject=subject) done.put((subject, None)) - except Exception as error: # pragma: no cover - surfaced through the parent assertion + except Exception as error: # noqa: BLE001 # pragma: no cover - sent to parent done.put((subject, repr(error))) diff --git a/harness/tests/test_decisions_retract.py b/harness/tests/test_decisions_retract.py index cf002cae..386709d7 100644 --- a/harness/tests/test_decisions_retract.py +++ b/harness/tests/test_decisions_retract.py @@ -57,8 +57,9 @@ def test_empty_session_returns_empty_list(tmp_path): def test_decision_type_literal_includes_retracted(): """decision_retracted e' un tipo valido (pydantic lo accetta).""" + from datetime import UTC, datetime + from tht.decisions import DecisionRecord - from datetime import datetime, UTC d = DecisionRecord( seq=1, ts=datetime.now(UTC), type="decision_retracted", subject="phase:4", retracts=1, diff --git a/harness/tests/test_doctor_cli.py b/harness/tests/test_doctor_cli.py index 58f195df..7cd1026f 100644 --- a/harness/tests/test_doctor_cli.py +++ b/harness/tests/test_doctor_cli.py @@ -5,7 +5,6 @@ from typer.testing import CliRunner from tht.cli import app - runner = CliRunner() diff --git a/harness/tests/test_dwh_adapters.py b/harness/tests/test_dwh_adapters.py index 8ffa8604..53d03062 100644 --- a/harness/tests/test_dwh_adapters.py +++ b/harness/tests/test_dwh_adapters.py @@ -1,12 +1,12 @@ import pytest from sqlalchemy.exc import OperationalError +from tht.adapters.dwh import PostgresDwhAdapter from tht.config import DatabaseConfig, RestConfig from tht.db.sampling import distinct_values_rest, sample_column_rest from tht.execute import ExecutionError from tht.ports import DistinctValues, DwhAdapter from tht.rest.client import RestError -from tht.adapters.dwh import PostgresDwhAdapter def postgres_factory(): diff --git a/harness/tests/test_dwh_port_contract.py b/harness/tests/test_dwh_port_contract.py index aad615c7..3f5f671f 100644 --- a/harness/tests/test_dwh_port_contract.py +++ b/harness/tests/test_dwh_port_contract.py @@ -5,10 +5,10 @@ import pytest from tht.execute import ExecResult, PlanSummary from tht.mschema.models import PhysicalSchema from tht.ports.dwh import ( + DistinctValues, DwhAdapter, DwhCapabilities, DwhHealth, - DistinctValues, UnsupportedCapability, ) @@ -55,10 +55,10 @@ def test_contract_types_are_public_and_capabilities_are_immutable(): def test_all_contract_types_are_exported_from_public_package(): + from tht.ports import DistinctValues as PublicDistinctValues from tht.ports import DwhAdapter as PublicDwhAdapter from tht.ports import DwhCapabilities as PublicDwhCapabilities from tht.ports import DwhHealth as PublicDwhHealth - from tht.ports import DistinctValues as PublicDistinctValues from tht.ports import UnsupportedCapability as PublicUnsupportedCapability result = PublicDistinctValues(values=["a"], truncated=True) diff --git a/harness/tests/test_dwh_preprocess_job.py b/harness/tests/test_dwh_preprocess_job.py index dd73fed4..e8d5f940 100644 --- a/harness/tests/test_dwh_preprocess_job.py +++ b/harness/tests/test_dwh_preprocess_job.py @@ -6,13 +6,16 @@ from typer.testing import CliRunner from tht.cli import app from tht.config import load_config -from tht.jobs.dwh_pipeline import DwhPreprocessPipeline -from tht.jobs.dwh_pipeline import active_generation_dir, config_dwh_binding, fingerprint -from tht.jobs.dwh_pipeline import resolve_dwh_snapshot -from tht.jobs.dwh_pipeline import lease_dwh_snapshot +from tht.jobs.dwh_pipeline import ( + DwhPreprocessPipeline, + active_generation_dir, + config_dwh_binding, + fingerprint, + lease_dwh_snapshot, + resolve_dwh_snapshot, +) from tht.jobs.locking import _lock_name - FP = "sha256:" + hashlib.sha256(b"test").hexdigest() @@ -104,9 +107,8 @@ def test_unowned_reads_fail_closed_without_creating_any_files(tmp_path): cfg = snapshot_config(tmp_path) with pytest.raises(Exception, match="not initialized"): resolve_dwh_snapshot(cfg) - with pytest.raises(Exception, match="not initialized"): - with lease_dwh_snapshot(cfg): - pass + with pytest.raises(Exception, match="not initialized"), lease_dwh_snapshot(cfg): + pass assert not (tmp_path / ".tht-dwh").exists() @@ -125,7 +127,7 @@ def test_writer_claim_allows_only_lock_and_empty_generations(tmp_path): pipeline = DwhPreprocessPipeline( workspace_id="demo", workspace_root=root.parent, config_fingerprint=FP, input_fingerprint=FP, - introspect=lambda output: calls.append("called"), + introspect=lambda output, calls=calls: calls.append("called"), build_lsh=lambda physical, output: None, ) with pytest.raises(Exception, match="unbound"): @@ -188,6 +190,7 @@ def test_owner_publication_remains_on_locked_root_when_path_is_swapped( monkeypatch, tmp_path, ): import pytest + import tht.jobs.dwh_pipeline as module real_replace = module.os.replace @@ -211,7 +214,7 @@ def test_owner_publication_remains_on_locked_root_when_path_is_swapped( introspect=lambda output: (_ for _ in ()).throw(AssertionError("callback called")), build_lsh=lambda physical, output: None, ) - with pytest.raises(Exception): + with pytest.raises(Exception, match="root"): pipeline.run() assert swapped assert (moved / "OWNER.json").is_file() @@ -394,7 +397,7 @@ def test_missing_active_with_generations_and_symlink_owner_marker_fail_closed(tm def _capture_error(operation): try: return operation() - except Exception as error: + except Exception as error: # noqa: BLE001 - helper returns the exact injected failure return error @@ -513,6 +516,7 @@ def test_unsafe_lsh_filename_is_rejected(tmp_path): def test_active_fsync_failure_restores_previous_pointer(monkeypatch, tmp_path): import os + import tht.jobs.dwh_pipeline as module def build(physical, output): for name in ("demo_lsh.pkl", "demo_minhashes.pkl", "demo_meta.json"): @@ -585,7 +589,7 @@ def test_snapshot_root_swap_after_lease_never_reads_replacement(monkeypatch, tmp try: with lease_dwh_snapshot(snapshot_config(tmp_path)) as snapshot: assert snapshot.physical.read_text() == "trusted" - except Exception as error: + except Exception as error: # noqa: BLE001 - either safe refusal path is acceptable assert "ACTIVE" in str(error) or "root" in str(error) assert swapped assert (replacement / "sentinel").read_text() == "replacement-secret" @@ -623,7 +627,9 @@ def test_snapshot_copies_each_validated_artifact_once_without_reopen(monkeypatch def test_reconcile_mismatch_closes_active_generation_fd(monkeypatch, tmp_path): import os from types import SimpleNamespace + import pytest + import tht.jobs.dwh_pipeline as module pipeline = DwhPreprocessPipeline( @@ -637,7 +643,7 @@ def test_reconcile_mismatch_closes_active_generation_fd(monkeypatch, tmp_path): real_active = module._active_generation_fd def mismatched_active(root_fd, binding): - generation, generation_fd = real_active(root_fd, binding) + _generation, generation_fd = real_active(root_fd, binding) return "f" * 32, generation_fd monkeypatch.setattr(module, "_active_generation_fd", mismatched_active) @@ -676,9 +682,10 @@ def test_pipeline_releases_materialized_snapshot_after_every_run(tmp_path): def test_corrupt_resume_checkpoint_releases_materialized_snapshot(tmp_path): - import tht.jobs.dwh_pipeline as module import pytest + import tht.jobs.dwh_pipeline as module + pipeline = DwhPreprocessPipeline( workspace_id="demo", workspace_root=tmp_path, config_fingerprint=FP, input_fingerprint=FP, @@ -699,9 +706,10 @@ def test_corrupt_resume_checkpoint_releases_materialized_snapshot(tmp_path): def test_job_spec_construction_failure_releases_materialized_snapshot(monkeypatch, tmp_path): - import tht.jobs.dwh_pipeline as module import pytest + import tht.jobs.dwh_pipeline as module + pipeline = DwhPreprocessPipeline( workspace_id="demo", workspace_root=tmp_path, config_fingerprint=FP, input_fingerprint=FP, diff --git a/harness/tests/test_evidence_facade_contract.py b/harness/tests/test_evidence_facade_contract.py index 6964d45f..f69b12a7 100644 --- a/harness/tests/test_evidence_facade_contract.py +++ b/harness/tests/test_evidence_facade_contract.py @@ -1,13 +1,11 @@ -from datetime import UTC, datetime import hashlib import inspect +from datetime import UTC, datetime from pathlib import Path from types import SimpleNamespace import pytest -from tht.evidence.corpus.models import CanonicalDocument, CorpusManifest -from tht.evidence.corpus.store import CorpusStore from tht.decisions import DecisionRecord from tht.evidence import ( acquire, @@ -25,6 +23,8 @@ from tht.evidence.contracts import ( EvidenceSourceErrorCategory, SourceObject, ) +from tht.evidence.corpus.models import CanonicalDocument, CorpusManifest +from tht.evidence.corpus.store import CorpusStore from tht.session.models import Candidate, SchemaLinking diff --git a/harness/tests/test_evidence_layout.py b/harness/tests/test_evidence_layout.py index bf5cb748..960dd4e9 100644 --- a/harness/tests/test_evidence_layout.py +++ b/harness/tests/test_evidence_layout.py @@ -1,6 +1,5 @@ from pathlib import Path - HARNESS_ROOT = Path(__file__).resolve().parents[1] LEGACY_PATHS = ( "tht/ports/evidence.py", diff --git a/harness/tests/test_evidence_port_contract.py b/harness/tests/test_evidence_port_contract.py index 81251d56..54b33c56 100644 --- a/harness/tests/test_evidence_port_contract.py +++ b/harness/tests/test_evidence_port_contract.py @@ -154,7 +154,7 @@ def test_datetimes_must_be_aware_and_are_normalized_to_utc(): source_id="source:a", uri="https://host/a", fingerprint="etag:abc", - modified_at=datetime(2026, 7, 12), + modified_at=datetime(2026, 7, 12), # noqa: DTZ001 - verifies rejection ) source = SourceObject( @@ -236,4 +236,8 @@ def test_model_copy_revalidates_source_and_acquired_records(): with pytest.raises(ValidationError, match="namespaced"): source.model_copy(update={"source_id": "invalid"}) with pytest.raises(ValidationError, match="timezone-aware"): - acquired.model_copy(update={"acquired_at": datetime(2026, 7, 12)}) + acquired.model_copy( + update={ + "acquired_at": datetime(2026, 7, 12) # noqa: DTZ001 - verifies rejection + } + ) diff --git a/harness/tests/test_execute_inject_limit.py b/harness/tests/test_execute_inject_limit.py index 3449640d..6861c8d6 100644 --- a/harness/tests/test_execute_inject_limit.py +++ b/harness/tests/test_execute_inject_limit.py @@ -46,7 +46,7 @@ def test_union_query_accepts_limit(): def test_with_cte_query_accepts_limit(): sql = "WITH cte AS (SELECT 1) SELECT * FROM cte" - out, injected = _inject_limit(sql, 10) + _out, injected = _inject_limit(sql, 10) assert injected is True diff --git a/harness/tests/test_formula.py b/harness/tests/test_formula.py index e7e636b7..006f22ee 100644 --- a/harness/tests/test_formula.py +++ b/harness/tests/test_formula.py @@ -70,6 +70,7 @@ def test_retrieve_empty_on_missing_dir(tmp_path): def test_concept_formula_decision_types_exist(): import typing + from tht.decisions import DecisionType args = typing.get_args(DecisionType) assert "concept_formula_approved" in args diff --git a/harness/tests/test_http_evidence_source.py b/harness/tests/test_http_evidence_source.py index 4bd24d0e..3dbfe89f 100644 --- a/harness/tests/test_http_evidence_source.py +++ b/harness/tests/test_http_evidence_source.py @@ -1,6 +1,7 @@ -import threading import socket +import threading from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from typing import ClassVar import pytest @@ -12,8 +13,8 @@ class Handler(BaseHTTPRequestHandler): etag_requests = 0 etag_body_responses = 0 redirect_target = "/redirected-v1" - redirect_request_validators = [] - final_request_validators = [] + redirect_request_validators: ClassVar[list[str | None]] = [] + final_request_validators: ClassVar[list[tuple[str, str | None]]] = [] def do_GET(self): if self.path.startswith("/etag"): @@ -198,7 +199,6 @@ class FakeSocket: class FakeResponse: status_code = 200 - headers = {} is_redirect = False def __init__(self, *, peer="127.0.0.1", stream_error=None, location=None): @@ -214,6 +214,7 @@ class FakeResponse: self.is_redirect = False self.status_code = 200 self.headers = {} + self.headers = {} def iter_content(self, chunk_size): if self.stream_error: @@ -265,7 +266,7 @@ def test_http_closes_response_when_streaming_fails(): source = HttpManifestEvidenceSource( ["https://example.test/doc"], allow_private_hosts=True ) - response = FakeResponse(stream_error=socket.timeout("read timed out")) + response = FakeResponse(stream_error=TimeoutError("read timed out")) source._session = FakeSession(response) with pytest.raises(EvidenceSourceError): list(source.discover()) diff --git a/harness/tests/test_job_locking.py b/harness/tests/test_job_locking.py index 0d952583..de3f3cd9 100644 --- a/harness/tests/test_job_locking.py +++ b/harness/tests/test_job_locking.py @@ -37,9 +37,11 @@ def test_same_workspace_and_job_are_exclusive_across_processes(tmp_path): def test_evidence_and_dwh_jobs_have_distinct_locks(tmp_path): - with WorkspaceJobLock(tmp_path, "demo", "evidence"): - with WorkspaceJobLock(tmp_path, "demo", "dwh"): - pass + with ( + WorkspaceJobLock(tmp_path, "demo", "evidence"), + WorkspaceJobLock(tmp_path, "demo", "dwh"), + ): + pass def test_lock_keys_cannot_escape_lock_directory(tmp_path): diff --git a/harness/tests/test_job_runner.py b/harness/tests/test_job_runner.py index 0874b047..62fe00b4 100644 --- a/harness/tests/test_job_runner.py +++ b/harness/tests/test_job_runner.py @@ -1,11 +1,12 @@ import json import os + import pytest from pydantic import ValidationError +import tht.jobs.runner as runner_module from tht.jobs.models import JobSpec from tht.jobs.runner import CorruptCheckpointError, StageArtifacts, run_job -import tht.jobs.runner as runner_module def _spec(tmp_path, **updates): diff --git a/harness/tests/test_lsh_job_resume.py b/harness/tests/test_lsh_job_resume.py index 81d9c4a0..bdb37c1e 100644 --- a/harness/tests/test_lsh_job_resume.py +++ b/harness/tests/test_lsh_job_resume.py @@ -3,7 +3,7 @@ import hashlib import pytest from tht.jobs.dwh_pipeline import DwhPreprocessPipeline - +from tht.jobs.runner import CorruptCheckpointError FP = "sha256:" + hashlib.sha256(b"test").hexdigest() @@ -67,12 +67,8 @@ def test_resume_rejects_a_different_stage_selection(tmp_path): introspect=lambda output: output.write_text("catalog"), build_lsh=lambda physical, output: None, ) - try: + with pytest.raises(CorruptCheckpointError, match="incompatible"): pipeline.run(("lsh",), resume_run_id=failed.run_id) - except Exception as error: - assert "incompatible" in str(error) - else: - raise AssertionError("resume with different stages must fail") def test_resume_rejects_tampered_succeeded_stage_artifact(tmp_path): diff --git a/harness/tests/test_manifest_pi_fields.py b/harness/tests/test_manifest_pi_fields.py index 89e9d158..e912db02 100644 --- a/harness/tests/test_manifest_pi_fields.py +++ b/harness/tests/test_manifest_pi_fields.py @@ -1,5 +1,5 @@ -from tht.session.store import create_session from tht.session.models import SessionManifest +from tht.session.store import create_session def test_manifest_persists_pi_fields(tmp_path): diff --git a/harness/tests/test_memory_metadata.py b/harness/tests/test_memory_metadata.py index 67185740..33f499f7 100644 --- a/harness/tests/test_memory_metadata.py +++ b/harness/tests/test_memory_metadata.py @@ -5,18 +5,18 @@ fonte delle memory. L'hit di search_similar deve bastare per ricostruire la deci completa. Per questo memory_vector_records mette subject/detail/rationale nel metadata del VectorRecord (pack_metadata li serializza nel jsonb via **record.metadata). """ -from datetime import datetime +from datetime import UTC, datetime from tht.memory import MemoryRecord, memory_vector_records def _record(**kw) -> MemoryRecord: - base = dict( - id="mem-x", ts=datetime(2025, 1, 1), session_id="s", decision_seq=1, - type="concept_clarified", subject="paziente attivo", detail="flag_attivo = TRUE", - rationale="perche' serve", question_context="dammi pazienti", - tables=[], concepts=["paziente attivo"], - ) + base = { + "id": "mem-x", "ts": datetime(2025, 1, 1, tzinfo=UTC), "session_id": "s", "decision_seq": 1, + "type": "concept_clarified", "subject": "paziente attivo", "detail": "flag_attivo = TRUE", + "rationale": "perche' serve", "question_context": "dammi pazienti", + "tables": [], "concepts": ["paziente attivo"], + } base.update(kw) return MemoryRecord(**base) diff --git a/harness/tests/test_memory_promotion.py b/harness/tests/test_memory_promotion.py index 420c08ba..0c1a3542 100644 --- a/harness/tests/test_memory_promotion.py +++ b/harness/tests/test_memory_promotion.py @@ -6,7 +6,7 @@ la rende un passo del workflow. Questi test fissano il contratto harness-side: - i candidati rifiutati al gate (memory_promotion_declined, detail "seq:") non vengono riproposti da reusable_promotions/preview_promotions. """ -from datetime import datetime +from datetime import UTC, datetime from tht.decisions import DecisionRecord, append_decision from tht.memory import ( @@ -23,7 +23,7 @@ from tht.workflow import load_workflow def test_promotion_decision_types_are_valid(): for t in ("memory_promoted", "memory_promotion_declined"): d = DecisionRecord( - seq=1, ts=datetime(2026, 1, 1), type=t, subject="fact_x", detail="seq:5" + seq=1, ts=datetime(2026, 1, 1, tzinfo=UTC), type=t, subject="fact_x", detail="seq:5" ) assert d.type == t @@ -36,14 +36,14 @@ def test_promotion_decision_types_min_phase_is_f8(): def _manifest() -> SessionManifest: return SessionManifest( - id="s1", created_at=datetime(2026, 1, 1), question="domanda originale", + id="s1", created_at=datetime(2026, 1, 1, tzinfo=UTC), question="domanda originale", database="db", schema="public", ) def test_declined_promotion_seqs_parses_seq_detail(): d = DecisionRecord( - seq=9, ts=datetime(2026, 1, 1), type="memory_promotion_declined", + seq=9, ts=datetime(2026, 1, 1, tzinfo=UTC), type="memory_promotion_declined", subject="fact_x", detail="seq:5", ) assert declined_promotion_seqs([d]) == {5} @@ -51,9 +51,9 @@ def test_declined_promotion_seqs_parses_seq_detail(): def test_declined_promotion_seqs_ignores_malformed_and_other_types(): ds = [ - DecisionRecord(seq=1, ts=datetime(2026, 1, 1), + DecisionRecord(seq=1, ts=datetime(2026, 1, 1, tzinfo=UTC), type="memory_promotion_declined", subject="x", detail=""), - DecisionRecord(seq=2, ts=datetime(2026, 1, 1), + DecisionRecord(seq=2, ts=datetime(2026, 1, 1, tzinfo=UTC), type="table_promoted", subject="x", detail="seq:3"), ] assert declined_promotion_seqs(ds) == set() @@ -100,11 +100,11 @@ def test_only_concept_clarified_is_proposed_or_promoted(tmp_path): def test_legacy_table_records_are_not_published_as_memory_vectors(): records = [ MemoryRecord( - id="mem-0001", ts=datetime(2026, 1, 1), session_id="s1", + id="mem-0001", ts=datetime(2026, 1, 1, tzinfo=UTC), session_id="s1", decision_seq=1, type="table_promoted", subject="fact_a", ), MemoryRecord( - id="mem-0002", ts=datetime(2026, 1, 1), session_id="s1", + id="mem-0002", ts=datetime(2026, 1, 1, tzinfo=UTC), session_id="s1", decision_seq=2, type="concept_clarified", subject="paziente attivo", detail="flag_attivo = TRUE", ), diff --git a/harness/tests/test_module_boundaries.py b/harness/tests/test_module_boundaries.py index 3c14ed4b..5233981d 100644 --- a/harness/tests/test_module_boundaries.py +++ b/harness/tests/test_module_boundaries.py @@ -7,7 +7,6 @@ from pathlib import Path import pytest - THT_ROOT = Path(__file__).resolve().parents[1] / "tht" DOMAIN_PACKAGES = ("evidence", "memory") diff --git a/harness/tests/test_mschema_eligibility.py b/harness/tests/test_mschema_eligibility.py index 98b7d8a7..29d465c7 100644 --- a/harness/tests/test_mschema_eligibility.py +++ b/harness/tests/test_mschema_eligibility.py @@ -4,7 +4,7 @@ Wide text (lettere di dimissione, note, anamnesi) is excluded everywhere; data comes only from numerics, enums, temporals, booleans, and short text. Annotation override wins over the physical classification. """ -from datetime import datetime +from datetime import UTC, datetime from tht.config import EligibilityConfig from tht.mschema.eligibility import classify_all, classify_column, effective_eligibility @@ -70,16 +70,16 @@ def test_array_type_is_wide_text(): def _schema_with(**columns) -> PhysicalSchema: return PhysicalSchema( - database="db", schema="dw", introspected_at=datetime(2025, 1, 1), + database="db", schema="dw", introspected_at=datetime(2025, 1, 1, tzinfo=UTC), tables={"t": TablePhysical(columns={k: ColumnPhysical(**v) for k, v in columns.items()})}, ) def test_classify_all_marks_wide_text_and_clears_examples(): schema = _schema_with( - note=dict(type="text", examples=["a" * 500, "b" * 400]), - cod=dict(type="varchar(10)", examples=["X", "Y"]), - etl_last_update=dict(type="timestamp", examples=[]), + note={"type": "text", "examples": ["a" * 500, "b" * 400]}, + cod={"type": "varchar(10)", "examples": ["X", "Y"]}, + etl_last_update={"type": "timestamp", "examples": []}, ) classify_all(schema, CFG) cols = schema.tables["t"].columns diff --git a/harness/tests/test_mschema_render.py b/harness/tests/test_mschema_render.py index 4c8f808b..88f9ced7 100644 --- a/harness/tests/test_mschema_render.py +++ b/harness/tests/test_mschema_render.py @@ -3,7 +3,7 @@ Catches port breaks in the render layer (markdown reviewer report, mschema-text ThothAI style, schema-dict for AV-SQL). Pure logic, no I/O. """ -from datetime import datetime +from datetime import UTC, datetime from tht.mschema.models import ( Annotations, @@ -20,7 +20,7 @@ def _fake_schema() -> PhysicalSchema: return PhysicalSchema( database="testdb", schema="dw", - introspected_at=datetime(2025, 1, 1, 0, 0, 0), + introspected_at=datetime(2025, 1, 1, 0, 0, 0, tzinfo=UTC), tables={ "dim_pazienti": TablePhysical( comment="Anagrafica pazienti", diff --git a/harness/tests/test_ollama_ensure.py b/harness/tests/test_ollama_ensure.py index efa5d3af..9b8da931 100644 --- a/harness/tests/test_ollama_ensure.py +++ b/harness/tests/test_ollama_ensure.py @@ -4,9 +4,9 @@ from types import SimpleNamespace from typer.testing import CliRunner -from tht.config import EmbeddingsConfig from tht.cli import ollama_cmd from tht.cli.ollama_cmd import ensure_ollama, ollama_app +from tht.config import EmbeddingsConfig def _cfg(**kw): diff --git a/harness/tests/test_phase_effective.py b/harness/tests/test_phase_effective.py index eefcd985..a0be250f 100644 --- a/harness/tests/test_phase_effective.py +++ b/harness/tests/test_phase_effective.py @@ -1,4 +1,4 @@ -from tht.decisions import append_decision, DecisionRecord +from tht.decisions import DecisionRecord, append_decision from tht.phase import current_phase, effective_decisions diff --git a/harness/tests/test_phase_reopen_order.py b/harness/tests/test_phase_reopen_order.py index 6976e074..1bc914a6 100644 --- a/harness/tests/test_phase_reopen_order.py +++ b/harness/tests/test_phase_reopen_order.py @@ -81,7 +81,7 @@ def test_successful_reopen_records_ledger_and_runs_teardown(tmp_path, monkeypatc calls = [] class _Report: - deleted_files = [] + deleted_files = () def _spy(_repository, snapshot, phase): # By the time teardown runs, the ledger already holds the reopen decision. diff --git a/harness/tests/test_pi_skill_projection.py b/harness/tests/test_pi_skill_projection.py index 1d14598f..4a9d1858 100644 --- a/harness/tests/test_pi_skill_projection.py +++ b/harness/tests/test_pi_skill_projection.py @@ -9,7 +9,6 @@ from tht.pi_skill_projection import ( render_projection, ) - BASELINE_SHA256 = "626a794071c095a4f20fffabb3bab901f05c101590adbdc58e45adfae56f3219" diff --git a/harness/tests/test_promoted_columns_for.py b/harness/tests/test_promoted_columns_for.py index 57ff20f7..d2075b88 100644 --- a/harness/tests/test_promoted_columns_for.py +++ b/harness/tests/test_promoted_columns_for.py @@ -1,4 +1,5 @@ import json + from tht.cli.sql_cmd import promoted_columns_for @@ -21,6 +22,6 @@ def test_promoted_columns_for(tmp_path): })) class Cfg: - class paths: # noqa: N801 + class paths: sessions = tmp_path assert promoted_columns_for(Cfg, sid) == {"dim_patient.cod_paz"} diff --git a/harness/tests/test_registry_evidence_config.py b/harness/tests/test_registry_evidence_config.py index 33d22a20..c0bff139 100644 --- a/harness/tests/test_registry_evidence_config.py +++ b/harness/tests/test_registry_evidence_config.py @@ -6,10 +6,10 @@ import yaml from pydantic import SecretStr from typer.testing import CliRunner -from tht.evidence.adapters import HttpManifestEvidenceSource -from tht.evidence import build_sources from tht.cli import app from tht.config import ConfigError, load_config +from tht.evidence import build_sources +from tht.evidence.adapters import HttpManifestEvidenceSource SIGNED_CANARY = "SIGNED-CANARY-QUERY" ACCESS_CANARY = "ACCESS-CANARY" diff --git a/harness/tests/test_s3_evidence_source.py b/harness/tests/test_s3_evidence_source.py index 43a865c9..994cec7f 100644 --- a/harness/tests/test_s3_evidence_source.py +++ b/harness/tests/test_s3_evidence_source.py @@ -99,7 +99,9 @@ def test_s3_rejects_leading_slash_prefix_empty_and_control_keys(): S3EvidenceSource(bucket="evidence", prefix="/clinical", client=Client()) for key in ("", "clinical/a\x00.md", "clinical/a\x7f.md"): client = Client() - client.list_objects_v2 = lambda **kwargs: {"Contents": [{"Key": key, "ETag": '"x"'}]} + client.list_objects_v2 = lambda key=key, **kwargs: { + "Contents": [{"Key": key, "ETag": '"x"'}] + } with pytest.raises(EvidenceSourceError): list(S3EvidenceSource(bucket="evidence", prefix="clinical/", client=client).discover()) diff --git a/harness/tests/test_schema_columns_cmd.py b/harness/tests/test_schema_columns_cmd.py index d9694e31..ca18cfd6 100644 --- a/harness/tests/test_schema_columns_cmd.py +++ b/harness/tests/test_schema_columns_cmd.py @@ -1,5 +1,5 @@ import json -from datetime import datetime +from datetime import UTC, datetime from typer.testing import CliRunner @@ -9,7 +9,7 @@ from tht.mschema.models import ColumnPhysical, PhysicalSchema, TablePhysical def _write_catalog(tmp_path): phys = PhysicalSchema( - database="d", schema="s", introspected_at=datetime(2026, 1, 1), + database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={ "dim_patient": TablePhysical( comment="Anagrafica", @@ -48,7 +48,7 @@ def _write_family_catalog(tmp_path): ) phys = PhysicalSchema( - database="d", schema="s", introspected_at=datetime(2026, 1, 1), + database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={ "fact_sost_impianto_pmk": _t("Sost PMK"), "fact_sost_impianto_crt_d": _t("Sost CRT-D"), diff --git a/harness/tests/test_schema_introspect_guard.py b/harness/tests/test_schema_introspect_guard.py index d5bccf5f..2d033412 100644 --- a/harness/tests/test_schema_introspect_guard.py +++ b/harness/tests/test_schema_introspect_guard.py @@ -1,16 +1,16 @@ -from datetime import datetime +from datetime import UTC, datetime from typer.testing import CliRunner from tht.cli import app -from tht.mschema.models import ColumnPhysical, PhysicalSchema, TablePhysical -from tht.config import ExamplesConfig from tht.cli.schema_cmd import _add_examples +from tht.config import ExamplesConfig +from tht.mschema.models import ColumnPhysical, PhysicalSchema, TablePhysical def _write_catalog(tmp_path): phys = PhysicalSchema( - database="d", schema="s", introspected_at=datetime(2026, 1, 1), + database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={ "dim_patient": TablePhysical( comment="Anagrafica", @@ -48,7 +48,7 @@ def test_introspect_fresh_root_initializes_through_writer_job(tmp_path, monkeypa cfg = _write_config(tmp_path) physical = PhysicalSchema( - database="d", schema="s", introspected_at=datetime(2026, 1, 1), + database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={"dim_patient": TablePhysical(columns={"id": ColumnPhysical(type="bigint")})}, ) @@ -90,7 +90,7 @@ def test_render_without_catalog_guides_fallback(tmp_path): def test_examples_skip_one_unreadable_column_and_continue(caplog): physical = PhysicalSchema( - database="d", schema="s", introspected_at=datetime(2026, 1, 1), + database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={"t": TablePhysical(columns={ "bad": ColumnPhysical(type="text"), "good": ColumnPhysical(type="text") })}, diff --git a/harness/tests/test_session_documents.py b/harness/tests/test_session_documents.py index 67ec41d5..66c612a7 100644 --- a/harness/tests/test_session_documents.py +++ b/harness/tests/test_session_documents.py @@ -1,20 +1,25 @@ """Tests for `tht session documents --json` and build_documents.""" import json -from datetime import datetime +from datetime import UTC, datetime from typer.testing import CliRunner +from tht.cli.session_cmd import session_app from tht.config import DatabaseConfig from tht.decisions import DecisionRecord from tht.session.models import SessionSnapshot -from tht.session.store import build_documents, build_snapshot_documents, create_session, new_session_manifest -from tht.cli.session_cmd import session_app +from tht.session.store import ( + build_documents, + build_snapshot_documents, + create_session, + new_session_manifest, +) def _db(): return DatabaseConfig( - database="testdb", user="u", password="p", # noqa: S106 - **{"schema": "public"}, + database="testdb", user="u", password="p", + schema="public", ) @@ -53,7 +58,7 @@ def test_build_documents_includes_existing_artifacts_only(tmp_path): def _decision(seq: int, type_: str, subject: str, detail: str = "", rationale: str = ""): return DecisionRecord( seq=seq, - ts=datetime(2026, 1, 1), + ts=datetime(2026, 1, 1, tzinfo=UTC), type=type_, subject=subject, detail=detail, diff --git a/harness/tests/test_session_list_json.py b/harness/tests/test_session_list_json.py index 71b5a53f..979764bc 100644 --- a/harness/tests/test_session_list_json.py +++ b/harness/tests/test_session_list_json.py @@ -14,8 +14,8 @@ def _make_db(): return DatabaseConfig( database="testdb", user="testuser", - password="testpass", # noqa: S106 - **{"schema": "public"}, + password="testpass", + schema="public", ) diff --git a/harness/tests/test_session_mutations.py b/harness/tests/test_session_mutations.py index d13545de..b077f979 100644 --- a/harness/tests/test_session_mutations.py +++ b/harness/tests/test_session_mutations.py @@ -17,8 +17,8 @@ from tht.session.store import ( def _db(): return DatabaseConfig( - database="testdb", user="u", password="p", # noqa: S106 - **{"schema": "public"}, + database="testdb", user="u", password="p", + schema="public", ) diff --git a/harness/tests/test_session_name.py b/harness/tests/test_session_name.py index cab20a4a..b5326774 100644 --- a/harness/tests/test_session_name.py +++ b/harness/tests/test_session_name.py @@ -8,15 +8,15 @@ import json from typer.testing import CliRunner -from tht.config import DatabaseConfig from tht.cli.session_cmd import session_app +from tht.config import DatabaseConfig from tht.session.store import _extract_name, _summarize, load_session def _db(): return DatabaseConfig( - database="testdb", user="u", password="p", # noqa: S106 - **{"schema": "public"}, + database="testdb", user="u", password="p", + schema="public", ) @@ -41,7 +41,7 @@ def test_extract_name_is_a_3_to_5_word_italian_summary(): def test_extract_name_falls_back_to_summarize_on_failure(monkeypatch): - import tht.session.store as store + from tht.session import store def boom(*a, **k): raise RuntimeError("yake down") diff --git a/harness/tests/test_session_repository.py b/harness/tests/test_session_repository.py index 0ca5b343..28ae5c27 100644 --- a/harness/tests/test_session_repository.py +++ b/harness/tests/test_session_repository.py @@ -7,12 +7,11 @@ from tht.decisions import DecisionInput from tht.session.filesystem_repository import FilesystemSessionRepository from tht.session.models import PrincipalContext, SessionManifest, local_principal from tht.session.repository import build_session_repository, resolve_principal -from tht.session.store import SessionError -from tht.session.store import create_session +from tht.session.store import SessionError, create_session def _db() -> DatabaseConfig: - return DatabaseConfig(database="testdb", schema="public", user="u", password="p") # noqa: S106 + return DatabaseConfig(database="testdb", schema="public", user="u", password="p") def _config(tmp_path): diff --git a/harness/tests/test_session_repository_workflow.py b/harness/tests/test_session_repository_workflow.py index 8608ce25..17e87778 100644 --- a/harness/tests/test_session_repository_workflow.py +++ b/harness/tests/test_session_repository_workflow.py @@ -1,7 +1,7 @@ import uuid from tht.decisions import DecisionInput -from tht.phase import current_phase, cte_plan, next_cte +from tht.phase import cte_plan, current_phase, next_cte from tht.session.filesystem_repository import FilesystemSessionRepository from tht.session.models import PrincipalContext, SessionManifest from tht.session.store import persist_verified_finalization diff --git a/harness/tests/test_set_schema_linking.py b/harness/tests/test_set_schema_linking.py index ba8f0091..1447f863 100644 --- a/harness/tests/test_set_schema_linking.py +++ b/harness/tests/test_set_schema_linking.py @@ -10,7 +10,7 @@ from tht.session.store import create_session, set_schema_linking def _db(): - return DatabaseConfig(database="testdb", user="u", password="p", **{"schema": "public"}) # noqa: S106 + return DatabaseConfig(database="testdb", user="u", password="p", schema="public") def test_writes_and_revalidates(tmp_path): diff --git a/harness/tests/test_set_schema_linking_cli.py b/harness/tests/test_set_schema_linking_cli.py index be5e4829..177e3922 100644 --- a/harness/tests/test_set_schema_linking_cli.py +++ b/harness/tests/test_set_schema_linking_cli.py @@ -9,7 +9,7 @@ from tht.session.store import create_session def _db(): - return DatabaseConfig(database="testdb", user="u", password="p", **{"schema": "public"}) # noqa: S106 + return DatabaseConfig(database="testdb", user="u", password="p", schema="public") def _patch_cfg(monkeypatch, tmp_path): diff --git a/harness/tests/test_sql_preview_json.py b/harness/tests/test_sql_preview_json.py index 5d0bba53..b3e71de6 100644 --- a/harness/tests/test_sql_preview_json.py +++ b/harness/tests/test_sql_preview_json.py @@ -6,7 +6,6 @@ Pure-logic tests (no DB needed): """ import json - from tht.execute.limit import inject_limit_offset diff --git a/harness/tests/test_sqlcheck.py b/harness/tests/test_sqlcheck.py index 696a9de1..efb665c3 100644 --- a/harness/tests/test_sqlcheck.py +++ b/harness/tests/test_sqlcheck.py @@ -17,12 +17,12 @@ from tht.sqlcheck import validate_sql def _schema() -> PhysicalSchema: """A tiny known schema for the object-existence checks.""" - from datetime import datetime + from datetime import UTC, datetime return PhysicalSchema( database="db", schema="dw", - introspected_at=datetime(2025, 1, 1), + introspected_at=datetime(2025, 1, 1, tzinfo=UTC), tables={ "dim_pazienti": TablePhysical( columns={ diff --git a/harness/tests/test_sync_schema_linking.py b/harness/tests/test_sync_schema_linking.py index 2c42b204..85060f17 100644 --- a/harness/tests/test_sync_schema_linking.py +++ b/harness/tests/test_sync_schema_linking.py @@ -8,8 +8,8 @@ from tht.session.store import create_session, set_schema_linking, sync_schema_li def _db(): return DatabaseConfig( - database="testdb", user="u", password="p", # noqa: S106 - **{"schema": "public"}, + database="testdb", user="u", password="p", + schema="public", ) diff --git a/harness/tests/test_value_grounding.py b/harness/tests/test_value_grounding.py index 100917d7..1c2a919f 100644 --- a/harness/tests/test_value_grounding.py +++ b/harness/tests/test_value_grounding.py @@ -64,7 +64,8 @@ def test_empty_hits_returns_empty(): def test_value_grounded_decision_type_exists(): # D14a adds the value_grounded decision type so the gate can record the # reviewer's choice of which column(s) anchor a cited value. - from tht.decisions import DecisionType import typing + + from tht.decisions import DecisionType args = typing.get_args(DecisionType) assert "value_grounded" in args diff --git a/harness/tests/test_workflow_observable_contract.py b/harness/tests/test_workflow_observable_contract.py index 29d90002..e1659f93 100644 --- a/harness/tests/test_workflow_observable_contract.py +++ b/harness/tests/test_workflow_observable_contract.py @@ -1,15 +1,15 @@ """Executable baseline for persisted workflow behavior touched by the refactor.""" +import hashlib from dataclasses import asdict from datetime import UTC, datetime -import hashlib import pytest -from tht.evidence.corpus.models import CanonicalDocument, CorpusManifest -from tht.evidence.corpus.store import CorpusStore from tht.decisions import DecisionRecord, append_decision from tht.evidence import project_session +from tht.evidence.corpus.models import CanonicalDocument, CorpusManifest +from tht.evidence.corpus.store import CorpusStore from tht.phase import current_phase, effective_decisions from tht.session.models import Candidate, SchemaLinking from tht.workflow import load_workflow diff --git a/harness/tht/adapters/dwh/postgres.py b/harness/tht/adapters/dwh/postgres.py index a926005e..09c71dbc 100644 --- a/harness/tht/adapters/dwh/postgres.py +++ b/harness/tht/adapters/dwh/postgres.py @@ -1,8 +1,8 @@ """Direct PostgreSQL implementation of the DWH port.""" -from tht.config import DatabaseConfig from sqlalchemy.exc import OperationalError, SQLAlchemyError +from tht.config import DatabaseConfig from tht.db import execute, sampling from tht.db.connection import can_create_in_schema, make_engine, ping, writable_tables from tht.db.introspect import introspect diff --git a/harness/tht/adapters/dwh/thoth_rest.py b/harness/tht/adapters/dwh/thoth_rest.py index db738dbd..aad20277 100644 --- a/harness/tht/adapters/dwh/thoth_rest.py +++ b/harness/tht/adapters/dwh/thoth_rest.py @@ -1,8 +1,8 @@ """Thoth/PostgREST implementation of the DWH port.""" from tht.config import DatabaseIdentityConfig, RestConfig -from tht.db.introspect import introspect_rest from tht.db import sampling +from tht.db.introspect import introspect_rest from tht.execute import ExecResult, ExecutionError, PlanSummary from tht.mschema.models import PhysicalSchema from tht.ports.dwh import DistinctValues, DwhCapabilities, DwhHealth diff --git a/harness/tht/cli/db_cmd.py b/harness/tht/cli/db_cmd.py index 73d1b8df..5eb2d4f0 100644 --- a/harness/tht/cli/db_cmd.py +++ b/harness/tht/cli/db_cmd.py @@ -1,6 +1,7 @@ from pathlib import Path import typer + from tht.adapters.factory import build_dwh from tht.cli.config_cmd import CONFIG_OPT from tht.config import ConfigError, load_config diff --git a/harness/tht/cli/lsh_cmd.py b/harness/tht/cli/lsh_cmd.py index 26fb97ed..da24d150 100644 --- a/harness/tht/cli/lsh_cmd.py +++ b/harness/tht/cli/lsh_cmd.py @@ -16,7 +16,7 @@ def _extract_lsh_values(dwh, physical, annotations, limit): continue try: distinct = dwh.distinct_values(table_name, column_name, limit=limit) - except Exception as exc: + except Exception as exc: # noqa: BLE001 - an unreadable DWH column is non-fatal skipped.append(SkippedColumn(table_name, column_name, f"errore: {exc}")) continue vals = [str(value) for value in distinct.values if value not in (None, "")] diff --git a/harness/tht/cli/memory_cmd.py b/harness/tht/cli/memory_cmd.py index 213a4be8..199bb6bd 100644 --- a/harness/tht/cli/memory_cmd.py +++ b/harness/tht/cli/memory_cmd.py @@ -459,8 +459,8 @@ def solved_search_cmd( from rich.table import Table from tht.cli.vector_cmd import make_embedder, open_searcher - from tht.ports.vector import VectorReadUnavailable, VectorStoreError from tht.memory import search_solved_questions + from tht.ports.vector import VectorReadUnavailable, VectorStoreError from tht.vectorstore.embeddings import EmbeddingsError cfg = _load_config_or_exit(config) diff --git a/harness/tht/cli/phase_cmd.py b/harness/tht/cli/phase_cmd.py index 37f318a6..d4389b37 100644 --- a/harness/tht/cli/phase_cmd.py +++ b/harness/tht/cli/phase_cmd.py @@ -12,8 +12,8 @@ import json import typer from tht.phase import ( - auto_advance_eligible, advance_problems, + auto_advance_eligible, current_phase, ) from tht.workflow import load_workflow @@ -93,12 +93,11 @@ def advance_cmd( if cur > wf.max_phase: typer.secho("Sessione già alla fase terminale.", fg=typer.colors.YELLOW) raise typer.Exit(0) - if auto: - if not auto_advance_eligible(snapshot): - problems = advance_problems(snapshot, cur) - for p in problems: - typer.echo(p) - raise typer.Exit(6) # needs human confirmation (gate contract) + if auto and not auto_advance_eligible(snapshot): + problems = advance_problems(snapshot, cur) + for p in problems: + typer.echo(p) + raise typer.Exit(6) # needs human confirmation (gate contract) session_repository(cfg).append_decisions( session, [{"type": "phase_approved", "subject": f"phase:{cur}"}] ) @@ -164,7 +163,7 @@ def _cfg(): ws = os.environ.get("THT_WORKSPACE") or os.environ.get("THT_CONFIG") config_path = Path(ws) if ws else Path("config/tht.yaml") try: - from tht.cli.schema_cmd import _load_config_or_exit # noqa: F401 (portato in Onda 1.4) + from tht.cli.schema_cmd import _load_config_or_exit return _load_config_or_exit(config_path) except ImportError: diff --git a/harness/tht/cli/preprocess_cmd.py b/harness/tht/cli/preprocess_cmd.py index 7190c5c8..7e852914 100644 --- a/harness/tht/cli/preprocess_cmd.py +++ b/harness/tht/cli/preprocess_cmd.py @@ -96,9 +96,9 @@ def run_from_config(config: Path, *, dry_run: bool = False, resume: str | None = from tht.adapters.factory import build_vector_store from tht.cli.schema_cmd import _load_config_or_exit from tht.cli.vector_cmd import make_embedder + from tht.evidence import build_preprocessing_pipeline, build_sources from tht.evidence.corpus.chunk import ChunkPolicy from tht.evidence.corpus.store import CorpusStore - from tht.evidence import build_preprocessing_pipeline, build_sources cfg = _load_config_or_exit(config) if cfg.embeddings is None: @@ -130,9 +130,9 @@ def gc_from_config(config: Path, *, dry_run: bool = False): from tht.adapters.factory import build_vector_store from tht.cli.schema_cmd import _load_config_or_exit from tht.cli.vector_cmd import make_embedder + from tht.evidence import build_preprocessing_pipeline, build_sources from tht.evidence.corpus.chunk import ChunkPolicy from tht.evidence.corpus.store import CorpusStore - from tht.evidence import build_preprocessing_pipeline, build_sources cfg = _load_config_or_exit(config) if cfg.embeddings is None: diff --git a/harness/tht/cli/schema_cmd.py b/harness/tht/cli/schema_cmd.py index 3adda3aa..2bfb77d5 100644 --- a/harness/tht/cli/schema_cmd.py +++ b/harness/tht/cli/schema_cmd.py @@ -303,7 +303,7 @@ def _suggest_fk_result(physical, annotations, *, sql_inputs: list[tuple[str, str @schema_app.command("check") def check_cmd( config: Path = CONFIG_OPT, - annotations: Path | None = typer.Option(None, "--annotations"), # noqa: B008 + annotations: Path | None = typer.Option(None, "--annotations"), reviewed_candidates: str | None = typer.Option(None, "--reviewed-candidates"), json_output: bool = typer.Option(False, "--json"), ) -> None: @@ -411,11 +411,11 @@ _GENERIC_PK_NAMES = {"id", "key", "code"} @schema_app.command("suggest-fks") def suggest_fks_cmd( config: Path = CONFIG_OPT, - from_sql: list[Path] = typer.Option( # noqa: B008 + from_sql: list[Path] = typer.Option( None, "--from-sql", help="Directory o file .sql approvati da cui minare i join reali (ripetibile).", ), - assume: list[str] = typer.Option( # noqa: B008 + assume: list[str] = typer.Option( None, "--assume", help="Disambigua una PK con piu' proprietari: col=tabella_ref " "(es. cod_paz=dim_patient). Ripetibile.", @@ -548,10 +548,10 @@ def render_cmd( format: str = typer.Option( "markdown", "--format", "-f", help="Formato: markdown | mschema-text | schema-dict" ), - tables: list[str] = typer.Option( # noqa: B008 + tables: list[str] = typer.Option( None, "--table", "-t", help="Limita alle tabelle indicate (ripetibile)." ), - output: Path = typer.Option(None, "--output", "-o", help="File di output (default stdout)."), # noqa: B008 + output: Path = typer.Option(None, "--output", "-o", help="File di output (default stdout)."), ) -> None: """Serializza mschema (physical + annotations) nel formato richiesto.""" import json diff --git a/harness/tht/cli/search_cmd.py b/harness/tht/cli/search_cmd.py index 757704d4..a17bcef9 100644 --- a/harness/tht/cli/search_cmd.py +++ b/harness/tht/cli/search_cmd.py @@ -258,9 +258,9 @@ def pack_cmd( build_retrieval_entries, validate_corpus_workspace, ) + from tht.memory import SOLVED_KIND from tht.ports.vector import VectorReadUnavailable, VectorStoreError from tht.search import combined_search, schema_tables - from tht.memory import SOLVED_KIND from tht.vectorstore.embeddings import EmbeddingsError cfg = _load_config_or_exit(config) diff --git a/harness/tht/cli/session_cmd.py b/harness/tht/cli/session_cmd.py index 17abc7d4..d85ff41a 100644 --- a/harness/tht/cli/session_cmd.py +++ b/harness/tht/cli/session_cmd.py @@ -515,12 +515,12 @@ def finalize_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OP promoted_tables_for, ) from tht.ctetest import CteError, CteTestRecord, _iter_json_objects + from tht.evidence import project_session from tht.execute import ExecutionError from tht.execute.warnings import plan_warnings, runtime_warnings, static_warnings - from tht.report import extract_reviewer_notes, render_validation_report - from tht.evidence import project_session from tht.phase import cte_plan as effective_cte_plan from tht.phase import effective_decisions + from tht.report import extract_reviewer_notes, render_validation_report from tht.session.models import SchemaLinking from tht.sqlcheck import validate_sql @@ -656,7 +656,7 @@ def finalize_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OP "Coppia domanda->SQL gia' aggiornata nel vectordb (nessun upsert).", fg=typer.colors.CYAN, ) - except Exception as e: + except Exception as e: # noqa: BLE001 - solved-question indexing is explicitly best effort typer.secho( f"ATTENZIONE: coppia domanda->SQL non indicizzata ({e}). " f"Recupera con `tht memory solved-index {session_id}`.", diff --git a/harness/tht/db/execute.py b/harness/tht/db/execute.py index 75fab3c6..701f8df4 100644 --- a/harness/tht/db/execute.py +++ b/harness/tht/db/execute.py @@ -5,10 +5,12 @@ from sqlalchemy import Engine from tht.execute import ( ExecResult, PlanSummary, - explain as _explain, require_positive_int, run_controlled, ) +from tht.execute import ( + explain as _explain, +) DEFAULT_TIMEOUT_MS = 30_000 diff --git a/harness/tht/db/fetch_ca.py b/harness/tht/db/fetch_ca.py index 52543e7d..e875f9bc 100644 --- a/harness/tht/db/fetch_ca.py +++ b/harness/tht/db/fetch_ca.py @@ -45,9 +45,11 @@ def fetch_chain_pem(host: str, port: int = 443, timeout: int = 30) -> list[str]: ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE try: - with socket.create_connection((host, port), timeout=timeout) as sock: - with ctx.wrap_socket(sock, server_hostname=host) as tls: - certs = _unverified_chain(tls) + with ( + socket.create_connection((host, port), timeout=timeout) as sock, + ctx.wrap_socket(sock, server_hostname=host) as tls, + ): + certs = _unverified_chain(tls) except (OSError, ssl.SSLError) as e: raise CaFetchError( f"Impossibile connettersi a {host}:{port} per recuperare i certificati: {e}" diff --git a/harness/tht/db/sampling.py b/harness/tht/db/sampling.py index ebc51bfb..12de04da 100644 --- a/harness/tht/db/sampling.py +++ b/harness/tht/db/sampling.py @@ -92,7 +92,7 @@ def add_examples(engine: Engine, physical: PhysicalSchema, cfg: ExamplesConfig) ''') try: rows = conn.execute(q, {"lim": cfg.max_per_column}).fetchall() - except Exception as e: # colonna non leggibile: si salta, non si interrompe + except Exception as e: # noqa: BLE001 - skip any unreadable DWH column logger.warning("Campionamento saltato per %s.%s: %s", table_name, column_name, e) continue column.examples = [str(r[0]) for r in rows] @@ -164,7 +164,7 @@ def unique_values_for_lsh( ''') try: rows = conn.execute(q, {"lim": cfg.max_values_per_column}).fetchall() - except Exception as e: + except Exception as e: # noqa: BLE001 - skip any unreadable DWH column skipped.append(SkippedColumn(table_name, column_name, f"errore: {e}")) continue vals = [str(r[0]) for r in rows] @@ -205,7 +205,7 @@ def unique_values_for_lsh_rest( rows = client.top_values( schema, table_name, column_name, cfg.max_values_per_column ) - except Exception as e: + except Exception as e: # noqa: BLE001 - skip any unreadable REST column skipped.append(SkippedColumn(table_name, column_name, f"errore: {e}")) continue vals = [str(r["value"]) for r in rows if r["value"] not in (None, "")] diff --git a/harness/tht/evidence/__init__.py b/harness/tht/evidence/__init__.py index fba55717..3648e8af 100644 --- a/harness/tht/evidence/__init__.py +++ b/harness/tht/evidence/__init__.py @@ -24,16 +24,15 @@ from tht.evidence.search import ( from tht.evidence.session import project_session from tht.evidence.sources import build_sources - __all__ = [ "AcquiredDocument", + "ActiveEvidenceSearcher", + "CorpusWorkspaceMismatchError", + "EvidenceEmbedder", "EvidenceSource", "EvidenceSourceError", "EvidenceSourceErrorCategory", - "EvidenceEmbedder", "SourceObject", - "ActiveEvidenceSearcher", - "CorpusWorkspaceMismatchError", "acquire", "active_searcher", "build_preprocessing_pipeline", diff --git a/harness/tht/evidence/adapters/s3.py b/harness/tht/evidence/adapters/s3.py index 5239a5f6..f065f60c 100644 --- a/harness/tht/evidence/adapters/s3.py +++ b/harness/tht/evidence/adapters/s3.py @@ -7,7 +7,10 @@ from datetime import UTC, datetime from urllib.parse import quote, urlsplit from tht.evidence.contracts import ( - AcquiredDocument, EvidenceSourceError, EvidenceSourceErrorCategory, SourceObject, + AcquiredDocument, + EvidenceSourceError, + EvidenceSourceErrorCategory, + SourceObject, ) diff --git a/harness/tht/evidence/corpus/models.py b/harness/tht/evidence/corpus/models.py index 22afca93..fe7ba324 100644 --- a/harness/tht/evidence/corpus/models.py +++ b/harness/tht/evidence/corpus/models.py @@ -15,7 +15,6 @@ from tht.evidence.contracts import ( validate_safe_metadata, ) - _NAMESPACED_ID = re.compile(r"^[a-z][a-z0-9_-]*:[A-Za-z0-9._:-]+$") _SHA256 = re.compile(r"^sha256:[0-9a-f]{64}$") diff --git a/harness/tht/evidence/corpus/normalize.py b/harness/tht/evidence/corpus/normalize.py index b55c6e88..5076077a 100644 --- a/harness/tht/evidence/corpus/normalize.py +++ b/harness/tht/evidence/corpus/normalize.py @@ -10,9 +10,8 @@ from pydantic import JsonValue, TypeAdapter, ValidationError from yaml.events import AliasEvent from yaml.nodes import MappingNode -from tht.evidence.corpus.models import CanonicalDocument from tht.evidence.contracts import AcquiredDocument, canonical_provenance_uri - +from tht.evidence.corpus.models import CanonicalDocument MAX_DOCUMENT_BYTES = 10 * 1024 * 1024 _CHARSET = re.compile(r"(?:^|;)\s*charset\s*=\s*[\"']?([^;\s\"']+)", re.IGNORECASE) diff --git a/harness/tht/evidence/corpus/pipeline.py b/harness/tht/evidence/corpus/pipeline.py index 23b67219..37bf7c31 100644 --- a/harness/tht/evidence/corpus/pipeline.py +++ b/harness/tht/evidence/corpus/pipeline.py @@ -4,6 +4,7 @@ from __future__ import annotations import hashlib import json +import logging import re import uuid from collections.abc import Mapping, Sequence @@ -11,17 +12,16 @@ from dataclasses import asdict, dataclass, field from datetime import UTC from pathlib import Path +import tht.evidence.acquisition as evidence_acquisition +from tht.evidence.contracts import EvidenceSource, SourceObject, canonical_provenance_uri from tht.evidence.corpus.chunk import ChunkPolicy, chunk from tht.evidence.corpus.models import CanonicalChunk, CanonicalDocument, CorpusManifest from tht.evidence.corpus.normalize import normalize from tht.evidence.corpus.store import CorpusStore -import tht.evidence.acquisition as evidence_acquisition -from tht.evidence.contracts import EvidenceSource, SourceObject, canonical_provenance_uri -from tht.ports.vector import VectorStore, VectorWriteRecord -from tht.vectorstore.records import VectorRecord from tht.jobs.models import JobSpec from tht.jobs.runner import JobContext, StageArtifacts, run_job, seal_stage_artifacts - +from tht.ports.vector import VectorStore, VectorWriteRecord +from tht.vectorstore.records import VectorRecord EVIDENCE_STAGE_IDS = ( "discover", @@ -33,6 +33,8 @@ EVIDENCE_STAGE_IDS = ( "retention_cleanup", ) +logger = logging.getLogger(__name__) + class PipelineError(RuntimeError): """Credential-free failure at the preprocessing boundary.""" @@ -212,14 +214,14 @@ class CorpusPipeline: if purge_vector: try: self.vector_store.delete_generation("evidence", generation, self.workspace_id) - except Exception: + except Exception: # noqa: BLE001 - retention reports per-generation failures failures.append({"generation": generation, "error": "vector cleanup failed"}) continue try: if purge_filesystem: self.store.discard(generation) evicted.append(generation) - except Exception: + except Exception: # noqa: BLE001 - retention reports per-generation failures failures.append({"generation": generation, "error": "filesystem cleanup failed"}) return {"status": "partial" if failures else "succeeded", "dry_run": dry_run, "active_generation": self.store.active_generation(), "evicted": evicted, @@ -378,7 +380,7 @@ class CorpusPipeline: try: active_assets_valid = active_assets_are_valid(previous) - except Exception: + except Exception: # noqa: BLE001 - any corrupt active asset disables reuse active_assets_valid = False reusable = ( active_assets_valid @@ -538,7 +540,7 @@ class CorpusPipeline: try: self.vector_store.delete_generation("evidence", generation, self.workspace_id) except Exception: - pass + logger.debug("Failed to clean the compensated vector generation", exc_info=True) write(context, "compensated.json", {"generation": generation}) def rotate_compensated_generation(context: JobContext) -> None: @@ -786,12 +788,12 @@ class CorpusPipeline: try: self.store.discard(generation) except Exception: - pass + logger.debug("Failed to discard the unpublished evidence generation", exc_info=True) if vector_written: try: self.vector_store.delete_generation("evidence", generation, self.workspace_id) except Exception: - pass + logger.debug("Failed to delete the unpublished vector generation", exc_info=True) @staticmethod def _vector_record( diff --git a/harness/tht/evidence/corpus/store.py b/harness/tht/evidence/corpus/store.py index af28b9a8..82f614bb 100644 --- a/harness/tht/evidence/corpus/store.py +++ b/harness/tht/evidence/corpus/store.py @@ -2,22 +2,21 @@ from __future__ import annotations -import json import fcntl +import hashlib +import json import os import re -import stat import shutil -import uuid -import hashlib +import stat import threading +import uuid +from contextlib import contextmanager from datetime import UTC, datetime from pathlib import Path -from contextlib import contextmanager from tht.evidence.corpus.models import CorpusManifest - _GENERATION = re.compile(r"^gen:[0-9a-f]{32}$") diff --git a/harness/tht/evidence/formula_store.py b/harness/tht/evidence/formula_store.py index 06a93d58..3c4cd53e 100644 --- a/harness/tht/evidence/formula_store.py +++ b/harness/tht/evidence/formula_store.py @@ -46,7 +46,7 @@ class ConceptFormula(BaseModel): return f"---\n{fm}---\n{self.sql}\n" @classmethod - def parse(cls, text: str) -> "ConceptFormula": + def parse(cls, text: str) -> ConceptFormula: if not text.startswith("---\n"): raise ValueError("frontmatter mancante (atteso '---\\n' iniziale)") try: @@ -55,7 +55,7 @@ class ConceptFormula(BaseModel): raise ValueError("frontmatter malformato") from e meta = yaml.safe_load(fm) if not isinstance(meta, dict): - raise ValueError("frontmatter non valido") + raise TypeError("frontmatter non valido") return cls.model_validate({**meta, "sql": body.strip("\n")}) diff --git a/harness/tht/evidence/preprocessing.py b/harness/tht/evidence/preprocessing.py index fd6c0c5e..ca7a726a 100644 --- a/harness/tht/evidence/preprocessing.py +++ b/harness/tht/evidence/preprocessing.py @@ -2,10 +2,10 @@ from typing import Protocol +from tht.evidence.contracts import EvidenceSource from tht.evidence.corpus.chunk import ChunkPolicy from tht.evidence.corpus.pipeline import CorpusPipeline from tht.evidence.corpus.store import CorpusStore -from tht.evidence.contracts import EvidenceSource from tht.ports.vector import VectorStore diff --git a/harness/tht/jobs/locking.py b/harness/tht/jobs/locking.py index 26ed43d1..edd09b24 100644 --- a/harness/tht/jobs/locking.py +++ b/harness/tht/jobs/locking.py @@ -9,6 +9,7 @@ import re import stat from pathlib import Path from types import TracebackType +from typing import Self class JobAlreadyRunningError(RuntimeError): @@ -34,7 +35,7 @@ class WorkspaceJobLock: ) self._fd: int | None = None - def acquire(self) -> "WorkspaceJobLock": + def acquire(self) -> WorkspaceJobLock: if self._fd is not None: raise RuntimeError("job lock is already held by this object") root_fd = os.open(self.path.parents[2], os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) @@ -85,7 +86,7 @@ class WorkspaceJobLock: finally: os.close(fd) - def __enter__(self) -> "WorkspaceJobLock": + def __enter__(self) -> Self: return self.acquire() def __exit__( diff --git a/harness/tht/jobs/models.py b/harness/tht/jobs/models.py index e85cd8a1..a10f5c23 100644 --- a/harness/tht/jobs/models.py +++ b/harness/tht/jobs/models.py @@ -7,8 +7,14 @@ from datetime import UTC, datetime from pathlib import Path from typing import Literal, Self -from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_validator - +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_serializer, + field_validator, + model_validator, +) _JOB_KEY = re.compile(r"^[a-z][a-z0-9_-]{0,63}$") _RUN_ID = re.compile(r"^[0-9a-f]{32}$") @@ -95,7 +101,7 @@ class JobSpec(_FrozenModel): data.update(update) return type(self).model_validate(data) - def with_resume(self, run_id: str) -> "JobSpec": + def with_resume(self, run_id: str) -> JobSpec: return self.model_copy(update={"resume_run_id": run_id}) @@ -121,7 +127,7 @@ class StageRun(_FrozenModel): ) @model_validator(mode="after") - def state_shape(self) -> "StageRun": + def state_shape(self) -> StageRun: if self.status == "pending" and any( value is not None for value in ( self.started_at, self.finished_at, self.error, self.effect_state, @@ -179,7 +185,7 @@ class JobRun(_FrozenModel): _resumed_from = field_validator("resumed_from")(_validate_run_id) @model_validator(mode="after") - def ledger_shape(self) -> "JobRun": + def ledger_shape(self) -> JobRun: names = [stage.name for stage in self.stages] if len(names) != len(set(names)): raise ValueError("stage identifiers must be unique") diff --git a/harness/tht/jobs/runner.py b/harness/tht/jobs/runner.py index 89f73b46..a3008c37 100644 --- a/harness/tht/jobs/runner.py +++ b/harness/tht/jobs/runner.py @@ -2,12 +2,12 @@ from __future__ import annotations -import json import hashlib +import json import os -import uuid -import stat import shutil +import stat +import uuid from collections.abc import Callable, Sequence from dataclasses import dataclass from pathlib import Path @@ -325,7 +325,7 @@ def run_job( _persist(checkpoint_path, run) try: stage_result = stage_callable(context) - except Exception: + except Exception: # noqa: BLE001 - stage failures are persisted as terminal reports failed = stage.model_copy( update={ "status": "failed", diff --git a/harness/tht/memory/solved.py b/harness/tht/memory/solved.py index 1e4dcfa0..a2fdc5d0 100644 --- a/harness/tht/memory/solved.py +++ b/harness/tht/memory/solved.py @@ -122,7 +122,7 @@ def index_solved_question_best_effort( store=store_factory(), embedder=embedder_factory(), ) - except Exception as error: + except Exception as error: # noqa: BLE001 - callers receive a best-effort outcome return SolvedIndexOutcome(upserted=None, error=str(error)) return SolvedIndexOutcome(upserted=upserted) diff --git a/harness/tht/mschema/render.py b/harness/tht/mschema/render.py index eb7b41a0..d20b9fdf 100644 --- a/harness/tht/mschema/render.py +++ b/harness/tht/mschema/render.py @@ -115,8 +115,8 @@ def to_markdown(physical: PhysicalSchema, annotations: Annotations | None = None lines = [ f"# Schema {physical.db_schema} ({physical.database})", "", - f"Introspezione: {physical.introspected_at.isoformat()} — " - f"{len(physical.tables)} tabelle", + (f"Introspezione: {physical.introspected_at.isoformat()} — " + f"{len(physical.tables)} tabelle"), ] for table_name, table in physical.tables.items(): lines += ["", f"## {table_name}", ""] diff --git a/harness/tht/pi_skill_projection.py b/harness/tht/pi_skill_projection.py index aa5e418a..ad3eb81b 100644 --- a/harness/tht/pi_skill_projection.py +++ b/harness/tht/pi_skill_projection.py @@ -1,9 +1,8 @@ """Deterministic builder for the single Pi-facing Thoth session skill.""" import argparse -from pathlib import Path import sys - +from pathlib import Path HARNESS_ROOT = Path(__file__).resolve().parents[1] SKILL_ROOT = HARNESS_ROOT / ".pi" / "skills" / "tht-sessione" diff --git a/harness/tht/ports/__init__.py b/harness/tht/ports/__init__.py index 1bd4e631..5ae5b73d 100644 --- a/harness/tht/ports/__init__.py +++ b/harness/tht/ports/__init__.py @@ -1,18 +1,18 @@ """Stable interfaces implemented by Thoth infrastructure adapters.""" from tht.ports.dwh import ( + DistinctValues, DwhAdapter, DwhCapabilities, DwhHealth, - DistinctValues, UnsupportedCapability, ) from tht.ports.vector import ( VectorCapabilities, VectorHealth, VectorHit, - VectorRecord, VectorReadUnavailable, + VectorRecord, VectorStore, VectorStoreError, VectorWriteRecord, @@ -20,16 +20,16 @@ from tht.ports.vector import ( ) __all__ = [ + "DistinctValues", "DwhAdapter", "DwhCapabilities", "DwhHealth", - "DistinctValues", "UnsupportedCapability", "VectorCapabilities", "VectorHealth", "VectorHit", - "VectorRecord", "VectorReadUnavailable", + "VectorRecord", "VectorStore", "VectorStoreError", "VectorWriteRecord", diff --git a/harness/tht/rest/client.py b/harness/tht/rest/client.py index 8d80333c..177375cc 100644 --- a/harness/tht/rest/client.py +++ b/harness/tht/rest/client.py @@ -40,7 +40,7 @@ class RestClient: try: body = resp.json() detail = body.get("message") or body.get("details") or resp.text - except Exception: + except (requests.exceptions.JSONDecodeError, AttributeError, TypeError): detail = resp.text return f"DWH REST rpc {fn} → HTTP {resp.status_code}: {detail}" diff --git a/harness/tht/session/filesystem_repository.py b/harness/tht/session/filesystem_repository.py index 98794e80..6787f9bc 100644 --- a/harness/tht/session/filesystem_repository.py +++ b/harness/tht/session/filesystem_repository.py @@ -9,8 +9,8 @@ import re import shutil import tempfile import uuid +from collections.abc import Sequence from pathlib import Path -from typing import Sequence import portalocker import yaml @@ -147,7 +147,7 @@ class FilesystemSessionRepository: return {} data = json.loads(path.read_text()) if not isinstance(data, dict): - raise ValueError(f"Invalid preferences: {path}") + raise TypeError(f"Invalid preferences: {path}") return data def set_preferences(self, preferences: dict) -> None: diff --git a/harness/tht/session/models.py b/harness/tht/session/models.py index b61ca5a5..f0e3f998 100644 --- a/harness/tht/session/models.py +++ b/harness/tht/session/models.py @@ -8,7 +8,7 @@ from typing import Literal, Self import portalocker import yaml -from pydantic import BaseModel, Field, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from tht.decisions import DecisionRecord diff --git a/harness/tht/session/postgres_repository.py b/harness/tht/session/postgres_repository.py index e998f5ea..771d8f2c 100644 --- a/harness/tht/session/postgres_repository.py +++ b/harness/tht/session/postgres_repository.py @@ -6,13 +6,13 @@ import hashlib import json import re import uuid +from collections.abc import Iterator, Sequence from contextlib import contextmanager from dataclasses import dataclass from datetime import UTC, datetime from importlib.resources import files from importlib.resources.abc import Traversable from pathlib import Path -from typing import Iterator, Sequence from sqlalchemy import Engine, create_engine, text from sqlalchemy.engine import URL, make_url @@ -204,7 +204,7 @@ class PostgresSessionRepository: self._runtime_role = runtime_role @classmethod - def from_config(cls, config, principal: PrincipalContext) -> "PostgresSessionRepository": + def from_config(cls, config, principal: PrincipalContext) -> PostgresSessionRepository: query = {"sslmode": config.sslmode} if config.sslrootcert is not None: query["sslrootcert"] = str(config.sslrootcert) diff --git a/harness/tht/session/repository.py b/harness/tht/session/repository.py index 5357a27a..279c5db5 100644 --- a/harness/tht/session/repository.py +++ b/harness/tht/session/repository.py @@ -3,7 +3,8 @@ from __future__ import annotations import os -from typing import Protocol, Sequence +from collections.abc import Sequence +from typing import Protocol from tht.decisions import DecisionInput, DecisionRecord from tht.session.models import PrincipalContext, SessionManifest, SessionSnapshot diff --git a/harness/tht/session/store.py b/harness/tht/session/store.py index 7097c81c..699de7cd 100644 --- a/harness/tht/session/store.py +++ b/harness/tht/session/store.py @@ -55,7 +55,7 @@ def _extract_name(question: str) -> str: try: extractor = yake.KeywordExtractor(lan="it", n=1, top=8, dedupLim=0.9) ranked = [k for k, _ in extractor.extract_keywords(q)] - except Exception: + except Exception: # noqa: BLE001 - keyword extraction has a deterministic fallback return _summarize(question) seen: set[str] = set() picked: list[str] = [] @@ -117,7 +117,7 @@ def create_session( from tht.workflow import load_workflow schema_version = load_workflow().schema_version - except Exception: + except Exception: # noqa: BLE001 - legacy sessions may predate workflow metadata schema_version = None manifest = SessionManifest( id=session_id, created_at=now, question=question, diff --git a/harness/tht/taskdoc.py b/harness/tht/taskdoc.py index 2aeea1e5..9a251aae 100644 --- a/harness/tht/taskdoc.py +++ b/harness/tht/taskdoc.py @@ -87,7 +87,7 @@ def generate_task_doc( wf = load_workflow() name = wf.phase_name(phase) header = f"## Task: fase {phase} ({name})" - except Exception: + except Exception: # noqa: BLE001 - task documents retain a phase-only fallback header = f"## Task: fase {phase}" parts.append(header) diff --git a/harness/tht/vendor/thoth_lsh.py b/harness/tht/vendor/thoth_lsh.py index 3007f3c3..ff13ac2d 100644 --- a/harness/tht/vendor/thoth_lsh.py +++ b/harness/tht/vendor/thoth_lsh.py @@ -4,11 +4,12 @@ """Core LSH (MinHash) per la ricerca di valori simili nei campi del database.""" import logging -from typing import Dict, List, Tuple from datasketch import MinHash, MinHashLSH from tqdm import tqdm +logger = logging.getLogger(__name__) + def create_minhash(signature_size: int, string: str, n_gram: int) -> MinHash: m = MinHash(num_perm=signature_size) @@ -33,7 +34,7 @@ NAME_LIKE_TOKENS: tuple[str, ...] = ( def skip_column( column_name: str, - column_values: List[str], + column_values: list[str], max_total_chars: int = 50000, max_avg_length: int = 20, name_tokens: tuple[str, ...] = NAME_LIKE_TOKENS, @@ -51,20 +52,20 @@ def jaccard_similarity(m1: MinHash, m2: MinHash) -> float: def create_lsh_index( - unique_values: Dict[str, Dict[str, List[str]]], + unique_values: dict[str, dict[str, list[str]]], signature_size: int, n_gram: int, threshold: float, verbose: bool = True, -) -> Tuple[MinHashLSH, Dict[str, Tuple[MinHash, str, str, str]]]: +) -> tuple[MinHashLSH, dict[str, tuple[MinHash, str, str, str]]]: lsh = MinHashLSH(threshold=threshold, num_perm=signature_size) - minhashes: Dict[str, Tuple[MinHash, str, str, str]] = {} + minhashes: dict[str, tuple[MinHash, str, str, str]] = {} total = sum( len(column_values) for table_values in unique_values.values() for column_values in table_values.values() ) - logging.info("Total unique values: %s", total) + logger.info("Total unique values: %s", total) progress_bar = tqdm(total=total, desc="Creating LSH") if verbose else None for table_name, table_values in unique_values.items(): diff --git a/harness/tht/workflow.py b/harness/tht/workflow.py index 882f8024..bd2807a3 100644 --- a/harness/tht/workflow.py +++ b/harness/tht/workflow.py @@ -82,9 +82,10 @@ def _collect_decision_mins(phases: list[PhaseSpec]) -> dict[str, int]: dtype = value else: continue - if isinstance(dtype, str): - if dtype not in mins or phase_num < mins[dtype]: - mins[dtype] = phase_num + if isinstance(dtype, str) and ( + dtype not in mins or phase_num < mins[dtype] + ): + mins[dtype] = phase_num else: scan(value, phase_num) elif isinstance(node, list):