From 5a597f919748efa079dc0a88b0c2be945ba664f4 Mon Sep 17 00:00:00 2001 From: Masato Onodera Date: Tue, 4 Aug 2026 13:52:20 +0900 Subject: [PATCH 1/2] test: add local pytest integration suite mirroring test_database.yml The Test Database CI workflow was the only way to exercise the full create-db -> q3c -> schema -> insert pipeline, and it only checked command exit codes. Add a Docker-backed pytest suite (tests/integration/) that reproduces the same CLI sequence locally and verifies the inserted rows via SQL, so this can be run and debugged without pushing to GitHub. Co-Authored-By: Claude Sonnet 5 --- docs/getting_started.md | 40 ++++ pyproject.toml | 8 +- tests/conftest.py | 75 +++++++ tests/docker/Dockerfile | 9 + tests/docker/docker-compose.test.yml | 26 +++ tests/integration/__init__.py | 0 tests/integration/conftest.py | 266 +++++++++++++++++++++++ tests/integration/test_catalog_active.py | 39 ++++ tests/integration/test_fluxstd.py | 34 +++ tests/integration/test_master_tables.py | 39 ++++ tests/integration/test_pointings.py | 59 +++++ tests/integration/test_setup.py | 29 +++ tests/integration/test_targets.py | 82 +++++++ uv.lock | 150 +++++++------ 14 files changed, 793 insertions(+), 63 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/docker/Dockerfile create mode 100644 tests/docker/docker-compose.test.yml create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/test_catalog_active.py create mode 100644 tests/integration/test_fluxstd.py create mode 100644 tests/integration/test_master_tables.py create mode 100644 tests/integration/test_pointings.py create mode 100644 tests/integration/test_setup.py create mode 100644 tests/integration/test_targets.py diff --git a/docs/getting_started.md b/docs/getting_started.md index fff26cf..6e408c8 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -145,6 +145,46 @@ print(df.head()) db.close() ``` +## Running Tests Locally + +The unit tests (`tests/test_*.py`, excluding `tests/integration/`) do not require a database +and can be run at any time: + +```bash +uv run pytest tests -m "not integration" +``` + +The integration tests (`tests/integration/`) exercise the same sequence of `pfs-targetdb-cli` +commands as the `Test Database` GitHub Actions workflow +(`.github/workflows/test_database.yml`): creating the database, installing the Q3C extension, +creating the schema, and inserting the example data in `examples/data/`, this time also +verifying the inserted rows with SQL queries. They require Docker (for a disposable +PostgreSQL + Q3C container) and are skipped automatically, with a reason, if Docker is not +available. + +```bash +# Docker must be running (e.g. Docker Desktop or OrbStack on macOS) +uv run pytest tests/integration -v +``` + +A dedicated PostgreSQL container is built from `tests/docker/Dockerfile` and started/stopped +automatically for the test session by `docker compose` (`tests/docker/docker-compose.test.yml`). +It listens on `localhost:15433` by default; set `TARGETDB_TEST_PG_PORT` to use a different port. +Its data directory is a tmpfs mount, so nothing is left on disk once the tests finish, and all +generated files (converted flux standard catalogs, transferred target lists) are written under a +pytest-managed temporary directory rather than into `examples/data/`. + +If a test fails and you want to inspect the database afterwards, pass `--keep-db` to leave the +container running: + +```bash +uv run pytest tests/integration -v --keep-db +psql -h localhost -p 15433 -U postgres -d test_targetdb + +# once done, stop the container manually +docker compose -f tests/docker/docker-compose.test.yml down -v +``` + ## Build the Documentation The documentation can be built by the following command: diff --git a/pyproject.toml b/pyproject.toml index 5b0c4d8..e436c65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,13 +45,19 @@ source = "https://github.com/Subaru-PFS/ets_target_database" pfs-targetdb-cli = "targetdb.cli.cli_main:app" [project.optional-dependencies] -dev = ["ipython", "pytest", "black", "ruff", "setuptools-scm>=8.0"] +dev = ["ipython", "pytest", "pytest-cov", "black", "ruff", "setuptools-scm>=8.0"] doc = ["mkdocs<2", "mkdocs-material[imaging]"] [build-system] requires = ["setuptools>=64", "setuptools-scm>=8.0", "wheel"] build-backend = "setuptools.build_meta" +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "integration: tests requiring a PostgreSQL container via Docker", +] + [tool.black] line-length = 88 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..2ec181a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import shutil +import subprocess +from functools import lru_cache +from pathlib import Path + +import pytest + +INTEGRATION_TESTS_DIR = Path(__file__).parent / "integration" + + +def pytest_addoption(parser): + parser.addoption( + "--keep-db", + action="store_true", + default=False, + help=( + "Do not tear down the PostgreSQL test container after the " + "integration test session. Useful for inspecting the database " + "after a failure, e.g. with " + "`psql -h localhost -p 15433 -U postgres -d test_targetdb`." + ), + ) + + +@lru_cache(maxsize=1) +def docker_available_reason(): + """ + Check whether a usable Docker daemon is available. + + Returns + ------- + reason : str or None + None if Docker is available and responsive, otherwise a short + string explaining why it is not, suitable for a pytest skip reason. + """ + if shutil.which("docker") is None: + return "docker executable not found on PATH" + + try: + result = subprocess.run( + ["docker", "info"], + capture_output=True, + timeout=10, + ) + except (OSError, subprocess.TimeoutExpired) as e: + return f"docker info failed to run: {e}" + + if result.returncode != 0: + return "docker daemon is not reachable (`docker info` failed)" + + return None + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "integration: tests requiring a PostgreSQL container via Docker", + ) + + +def pytest_collection_modifyitems(config, items): + reason = docker_available_reason() + + for item in items: + try: + item.path.relative_to(INTEGRATION_TESTS_DIR) + except ValueError: + continue + + item.add_marker(pytest.mark.integration) + + if reason is not None: + item.add_marker(pytest.mark.skip(reason=f"Docker unavailable: {reason}")) diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile new file mode 100644 index 0000000..d549c60 --- /dev/null +++ b/tests/docker/Dockerfile @@ -0,0 +1,9 @@ +# PostgreSQL image with the Q3C extension pre-installed, mirroring the +# packages installed on-the-fly in .github/workflows/test_database.yml. +# Building this once and reusing the image is much faster than the CI +# approach of installing q3c into a freshly-started container every run. +FROM postgres:16 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends postgresql-16-q3c \ + && rm -rf /var/lib/apt/lists/* diff --git a/tests/docker/docker-compose.test.yml b/tests/docker/docker-compose.test.yml new file mode 100644 index 0000000..be67075 --- /dev/null +++ b/tests/docker/docker-compose.test.yml @@ -0,0 +1,26 @@ +# Standalone PostgreSQL + Q3C stack used by the pytest integration tests +# (tests/integration/). Kept separate from examples/docker/, which is a +# developer-facing, persistent local database and not meant to be +# started/stopped/discarded by the test suite. +name: targetdb-test + +services: + postgres: + build: + context: . + dockerfile: Dockerfile + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - "${TARGETDB_TEST_PG_PORT:-15433}:5432" + # Data lives in tmpfs: each test session starts from a clean database + # and nothing is left on disk after teardown. + tmpfs: + - /var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 5s + retries: 30 diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..0aaad31 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +Session-scoped fixture pipeline that reproduces, locally, the sequence of +`pfs-targetdb-cli` commands run by .github/workflows/test_database.yml: + + postgres_service -> db_config -> database -> q3c_installed -> schema + -> master_data -> fluxstd_data + -> master_data -> target_data -> pointing_data + +Each fixture requests the one(s) it depends on, so a test only needs to +request the fixture for the stage it cares about (and pytest builds +everything upstream automatically). All of it is torn down once per test +session (or left running with `--keep-db`). + +Unlike the CI workflow, output files (prep-fluxstd output, transferred +target lists) are written under a pytest-managed temporary directory +instead of into the repository, so running the suite never leaves the +working tree dirty. +""" + +import subprocess +from pathlib import Path + +import pandas as pd +import pytest +from astropy.table import Table +from sqlalchemy import create_engine, text +from typer.testing import CliRunner + +from targetdb.cli.cli_main import app +from targetdb.utils import get_url_object, load_config + +REPO_ROOT = Path(__file__).resolve().parents[2] +DOCKER_DIR = REPO_ROOT / "tests" / "docker" +COMPOSE_FILE = DOCKER_DIR / "docker-compose.test.yml" + +EXAMPLES_DATA = REPO_ROOT / "examples" / "data" +TARGETS_DATA_DIR = EXAMPLES_DATA / "targets" +FLUXSTD_INPUT_DIR = EXAMPLES_DATA / "fluxstd" / "Fstar_v3.3" / "feather-original" + +# Master tables inserted in the same order as the "Insert example data" CI step. +MASTER_TABLE_FILES = [ + ("target_type", "target_types.csv"), + ("filter_name", "filter_names.csv"), + ("partner", "partner.csv"), + ("pfs_arm", "pfs_arm.csv"), + ("proposal_category", "proposal_category.csv"), + ("proposal", "proposals.csv"), + ("input_catalog", "input_catalogs.csv"), +] + +PG_PORT = 15433 + +_cli_runner = CliRunner() + + +def run_cli(*args): + """ + Invoke the ``pfs-targetdb-cli`` Typer app in-process and assert success. + + Running in-process (rather than via subprocess) means failures surface + as normal Python tracebacks/output in the pytest report instead of an + opaque non-zero exit code. + """ + result = _cli_runner.invoke(app, [str(a) for a in args], catch_exceptions=False) + assert result.exit_code == 0, ( + f"CLI command {args!r} failed with exit code {result.exit_code}\n" + f"Output:\n{result.output}" + ) + return result + + +def count_rows(engine, table): + with engine.connect() as conn: + return conn.execute(text(f"SELECT count(*) FROM {table}")).scalar_one() + + +def csv_row_count(path): + return len(pd.read_csv(path)) + + +def ecsv_row_count(path): + return len(Table.read(path, format="ascii.ecsv")) + + +def _run_compose(*args): + cmd = ["docker", "compose", "-f", str(COMPOSE_FILE), *args] + result = subprocess.run(cmd, cwd=DOCKER_DIR, capture_output=True, text=True) + if result.returncode != 0: + raise RuntimeError( + f"Command failed: {' '.join(cmd)}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + return result + + +@pytest.fixture(scope="session") +def postgres_service(request): + """Bring up the PostgreSQL+Q3C container for the whole test session.""" + _run_compose("up", "-d", "--build", "--wait", "--wait-timeout", "120") + try: + yield {"host": "localhost", "port": PG_PORT} + finally: + if request.config.getoption("--keep-db"): + print( + f"\n--keep-db: leaving the test PostgreSQL container running " + f"on port {PG_PORT}. Stop it manually with:\n" + f" docker compose -f {COMPOSE_FILE} down -v" + ) + else: + _run_compose("down", "-v", "--remove-orphans") + + +@pytest.fixture(scope="session") +def work_dir(tmp_path_factory): + """Scratch directory for generated config/data files, unique per session.""" + return tmp_path_factory.mktemp("targetdb-integration") + + +@pytest.fixture(scope="session") +def db_config(postgres_service, work_dir): + """Write a test dbconf.toml pointing at the container and return its path.""" + config_path = work_dir / "test_db_config.toml" + config_path.write_text( + f"""\ +[targetdb.db] +dialect = "postgresql" +user = "postgres" +password = "postgres" +host = "{postgres_service["host"]}" +port = {postgres_service["port"]} +dbname = "test_targetdb" + +[uploader] +host = "localhost" +data_dir = "{EXAMPLES_DATA / "uploader"}" +""" + ) + return config_path + + +@pytest.fixture(scope="session") +def engine(database, db_config): + config = load_config(str(db_config)) + eng = create_engine(get_url_object(config)) + yield eng + eng.dispose() + + +@pytest.fixture(scope="session") +def database(db_config): + run_cli("create-db", "-c", db_config) + + +@pytest.fixture(scope="session") +def q3c_installed(database, db_config): + run_cli("install-q3c", "-c", db_config) + + +@pytest.fixture(scope="session") +def schema(q3c_installed, db_config): + run_cli("create-schema", "-c", db_config) + + +@pytest.fixture(scope="session") +def master_data(schema, db_config): + """Insert the master/reference tables shared by every downstream stage.""" + for table, filename in MASTER_TABLE_FILES: + run_cli( + "insert", EXAMPLES_DATA / filename, "-c", db_config, "-t", table, "--commit" + ) + # input_catalog entry required by the flux standard star catalog. + run_cli( + "insert", + EXAMPLES_DATA / "input_catalog_fluxstd.csv", + "-c", + db_config, + "-t", + "input_catalog", + "--commit", + ) + + +@pytest.fixture(scope="session") +def fluxstd_data(master_data, db_config, work_dir): + """Run prep-fluxstd and insert the resulting rows into the fluxstd table.""" + output_dir = work_dir / "fluxstd_feather" + run_cli( + "prep-fluxstd", + FLUXSTD_INPUT_DIR, + output_dir, + "--version", + "3.3", + "--input_catalog_id", + "3006", + "--rename-cols", + '{"fstar_gaia": "is_fstar_gaia"}', + "--format", + "feather", + ) + generated_files = sorted(output_dir.glob("*.feather")) + assert generated_files, f"prep-fluxstd produced no feather files in {output_dir}" + + for f in generated_files: + run_cli("insert", f, "-c", db_config, "-t", "fluxstd", "--commit") + + return {"output_dir": output_dir, "files": generated_files} + + +@pytest.fixture(scope="session") +def target_data(master_data, db_config, work_dir): + """Insert proposals/catalogs for the target examples, then transfer and insert targets.""" + run_cli( + "insert", + TARGETS_DATA_DIR / "example_proposals.csv", + "-c", + db_config, + "-t", + "proposal", + "--commit", + ) + run_cli( + "insert", + TARGETS_DATA_DIR / "example_input_catalogs.csv", + "-c", + db_config, + "-t", + "input_catalog", + "--commit", + "--fetch", + ) + + local_dir = work_dir / "targets" + run_cli( + "transfer-targets", + TARGETS_DATA_DIR / "example_input_catalogs.csv", + "-c", + db_config, + "--local-dir", + local_dir, + "--force", + ) + run_cli( + "insert-targets", + TARGETS_DATA_DIR / "example_input_catalogs.csv", + "-c", + db_config, + "--data-dir", + local_dir, + "--commit", + ) + + return {"local_dir": local_dir} + + +@pytest.fixture(scope="session") +def pointing_data(target_data, db_config): + run_cli( + "insert-pointings", + TARGETS_DATA_DIR / "example_input_catalogs.csv", + "-c", + db_config, + "--data-dir", + target_data["local_dir"], + "--commit", + ) diff --git a/tests/integration/test_catalog_active.py b/tests/integration/test_catalog_active.py new file mode 100644 index 0000000..c669f42 --- /dev/null +++ b/tests/integration/test_catalog_active.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Verify update-catalog-active only changes the targeted input_catalog row.""" + +from sqlalchemy import text + +from .conftest import run_cli + +TARGET_CATALOG_ID = 1004 + + +def _read_active_flags(engine): + with engine.connect() as conn: + rows = conn.execute(text("SELECT input_catalog_id, active FROM input_catalog")) + return dict(rows.all()) + + +def test_update_catalog_active_only_affects_target_row(engine, master_data, db_config): + before = _read_active_flags(engine) + assert TARGET_CATALOG_ID in before, ( + f"expected input_catalog_id={TARGET_CATALOG_ID} from examples/data/input_catalogs.csv" + ) + + run_cli( + "update-catalog-active", + TARGET_CATALOG_ID, + "true", + "-c", + db_config, + "--verbose", + "--commit", + ) + + after = _read_active_flags(engine) + + assert after[TARGET_CATALOG_ID] is True + + unaffected_before = {k: v for k, v in before.items() if k != TARGET_CATALOG_ID} + unaffected_after = {k: v for k, v in after.items() if k != TARGET_CATALOG_ID} + assert unaffected_after == unaffected_before diff --git a/tests/integration/test_fluxstd.py b/tests/integration/test_fluxstd.py new file mode 100644 index 0000000..8b2619b --- /dev/null +++ b/tests/integration/test_fluxstd.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Verify prep-fluxstd output and the resulting fluxstd table contents.""" + +import pandas as pd +from sqlalchemy import text + +from .conftest import FLUXSTD_INPUT_DIR, count_rows + +INPUT_FEATHER = FLUXSTD_INPUT_DIR / "ra354.8_354.9_dec-40.0_90.0.feather" + + +def test_prep_fluxstd_generates_output_files(fluxstd_data): + assert fluxstd_data["files"], "expected at least one generated feather file" + for f in fluxstd_data["files"]: + assert f.exists() + + +def test_fluxstd_row_count_matches_input(engine, fluxstd_data): + expected = len(pd.read_feather(INPUT_FEATHER)) + assert count_rows(engine, "fluxstd") == expected + + +def test_fluxstd_rows_tagged_with_catalog_and_version(engine, fluxstd_data): + with engine.connect() as conn: + rows = conn.execute( + text("SELECT DISTINCT input_catalog_id, version FROM fluxstd") + ).fetchall() + assert rows == [(3006, "3.3")] + + +def test_fluxstd_rename_cols_applied(fluxstd_data): + df = pd.read_feather(fluxstd_data["files"][0]) + assert "is_fstar_gaia" in df.columns + assert "fstar_gaia" not in df.columns diff --git a/tests/integration/test_master_tables.py b/tests/integration/test_master_tables.py new file mode 100644 index 0000000..130d22e --- /dev/null +++ b/tests/integration/test_master_tables.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Verify the master/reference tables inserted from examples/data/*.csv.""" + +import pytest +from sqlalchemy import text + +from .conftest import EXAMPLES_DATA, MASTER_TABLE_FILES, count_rows, csv_row_count + +# input_catalog receives an extra insert (input_catalog_fluxstd.csv) on top of +# input_catalogs.csv, so it is checked separately below rather than via the +# generic 1:1 CSV-to-table comparison. +SIMPLE_MASTER_TABLES = [ + (table, filename) for table, filename in MASTER_TABLE_FILES if table != "input_catalog" +] + + +@pytest.mark.parametrize("table, filename", SIMPLE_MASTER_TABLES) +def test_master_table_row_count(engine, master_data, table, filename): + expected = csv_row_count(EXAMPLES_DATA / filename) + assert count_rows(engine, table) == expected + + +def test_input_catalog_row_count(engine, master_data): + expected = csv_row_count(EXAMPLES_DATA / "input_catalogs.csv") + csv_row_count( + EXAMPLES_DATA / "input_catalog_fluxstd.csv" + ) + assert count_rows(engine, "input_catalog") == expected + + +def test_fluxstd_input_catalog_registered(engine, master_data): + with engine.connect() as conn: + row = conn.execute( + text( + "SELECT input_catalog_name FROM input_catalog " + "WHERE input_catalog_id = 3006" + ) + ).fetchone() + assert row is not None + assert row[0] == "Fstar_v3.3" diff --git a/tests/integration/test_pointings.py b/tests/integration/test_pointings.py new file mode 100644 index 0000000..84b4e1d --- /dev/null +++ b/tests/integration/test_pointings.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Verify insert-pointings only creates user_pointing rows for catalogs +flagged with is_user_pointing=True, and that the row count and catalog +linkage are correct.""" + +import pandas as pd +from sqlalchemy import text + +from .conftest import TARGETS_DATA_DIR, count_rows, ecsv_row_count + +INPUT_CATALOGS_CSV = TARGETS_DATA_DIR / "example_input_catalogs.csv" + + +def test_user_pointing_only_for_flagged_catalogs(engine, pointing_data, target_data): + catalogs_df = pd.read_csv(INPUT_CATALOGS_CSV) + flagged = catalogs_df[catalogs_df["is_user_pointing"]] + not_flagged = catalogs_df[~catalogs_df["is_user_pointing"]] + assert len(flagged) > 0, "test fixture data must include a user-pointing catalog" + + local_dir = target_data["local_dir"] + + with engine.connect() as conn: + for _, row in flagged.iterrows(): + upload_dir = next(local_dir.glob(f"????????-??????-{row['upload_id']}")) + expected = ecsv_row_count(upload_dir / f"ppc_{row['upload_id']}.ecsv") + + actual = conn.execute( + text( + "SELECT count(*) FROM user_pointing up " + "JOIN input_catalog c ON up.input_catalog_id = c.input_catalog_id " + "WHERE c.upload_id = :upload_id" + ), + {"upload_id": row["upload_id"]}, + ).scalar_one() + assert actual == expected + + for _, row in not_flagged.iterrows(): + actual = conn.execute( + text( + "SELECT count(*) FROM user_pointing up " + "JOIN input_catalog c ON up.input_catalog_id = c.input_catalog_id " + "WHERE c.upload_id = :upload_id" + ), + {"upload_id": row["upload_id"]}, + ).scalar_one() + assert actual == 0 + + +def test_user_pointing_total_matches_flagged_catalogs(engine, pointing_data, target_data): + catalogs_df = pd.read_csv(INPUT_CATALOGS_CSV) + flagged = catalogs_df[catalogs_df["is_user_pointing"]] + + local_dir = target_data["local_dir"] + total_expected = 0 + for _, row in flagged.iterrows(): + upload_dir = next(local_dir.glob(f"????????-??????-{row['upload_id']}")) + total_expected += ecsv_row_count(upload_dir / f"ppc_{row['upload_id']}.ecsv") + + assert count_rows(engine, "user_pointing") == total_expected diff --git a/tests/integration/test_setup.py b/tests/integration/test_setup.py new file mode 100644 index 0000000..a38041c --- /dev/null +++ b/tests/integration/test_setup.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Verify the initial database setup: q3c extension and table creation.""" + +from sqlalchemy import text + +from targetdb import models + + +def test_q3c_extension_installed(engine, q3c_installed): + with engine.connect() as conn: + extnames = {row[0] for row in conn.execute(text("SELECT extname FROM pg_extension"))} + assert "q3c" in extnames + + +def test_all_model_tables_created(engine, schema): + with engine.connect() as conn: + existing_tables = { + row[0] + for row in conn.execute( + text( + "SELECT table_name FROM information_schema.tables " + "WHERE table_schema = 'public'" + ) + ) + } + + expected_tables = set(models.Base.metadata.tables.keys()) + missing = expected_tables - existing_tables + assert not missing, f"Tables missing after create-schema: {sorted(missing)}" diff --git a/tests/integration/test_targets.py b/tests/integration/test_targets.py new file mode 100644 index 0000000..bdbbddd --- /dev/null +++ b/tests/integration/test_targets.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Verify transfer-targets + insert-targets: local data is fetched for every +catalog, and the resulting `target` rows match the transferred ECSV files +and reference valid input_catalog/proposal/target_type rows. +""" + +import pandas as pd +import pytest +from sqlalchemy import text + +from .conftest import TARGETS_DATA_DIR, count_rows, ecsv_row_count + +INPUT_CATALOGS_CSV = TARGETS_DATA_DIR / "example_input_catalogs.csv" + + +@pytest.fixture(scope="module") +def catalogs_df(): + return pd.read_csv(INPUT_CATALOGS_CSV) + + +def test_transfer_targets_downloads_all_catalogs(target_data, catalogs_df): + local_dir = target_data["local_dir"] + for upload_id in catalogs_df["upload_id"]: + matches = list(local_dir.glob(f"????????-??????-{upload_id}")) + assert len(matches) == 1, ( + f"expected exactly one transferred data dir for upload_id={upload_id}, " + f"got {matches}" + ) + + +def test_input_catalogs_registered_with_upload_id(engine, target_data, catalogs_df): + with engine.connect() as conn: + rows = conn.execute( + text( + "SELECT input_catalog_name, upload_id FROM input_catalog " + "WHERE upload_id IS NOT NULL" + ) + ).fetchall() + registered = {name: upload_id for name, upload_id in rows} + + for _, row in catalogs_df.iterrows(): + assert registered.get(row["input_catalog_name"]) == row["upload_id"] + + +def test_target_row_count_matches_ecsv_inputs(engine, target_data): + local_dir = target_data["local_dir"] + total_expected = 0 + for upload_dir in sorted(local_dir.glob("????????-??????-*")): + upload_id = upload_dir.name.split("-", 2)[2] + total_expected += ecsv_row_count(upload_dir / f"target_{upload_id}.ecsv") + + assert count_rows(engine, "target") == total_expected + + +def test_targets_reference_valid_foreign_keys(engine, target_data): + with engine.connect() as conn: + orphan_catalog = conn.execute( + text( + "SELECT count(*) FROM target t " + "LEFT JOIN input_catalog c ON t.input_catalog_id = c.input_catalog_id " + "WHERE c.input_catalog_id IS NULL" + ) + ).scalar_one() + orphan_proposal = conn.execute( + text( + "SELECT count(*) FROM target t " + "LEFT JOIN proposal p ON t.proposal_id = p.proposal_id " + "WHERE t.proposal_id IS NOT NULL AND p.proposal_id IS NULL" + ) + ).scalar_one() + orphan_target_type = conn.execute( + text( + "SELECT count(*) FROM target t " + "LEFT JOIN target_type tt ON t.target_type_id = tt.target_type_id " + "WHERE t.target_type_id IS NOT NULL AND tt.target_type_id IS NULL" + ) + ).scalar_one() + + assert orphan_catalog == 0 + assert orphan_proposal == 0 + assert orphan_target_type == 0 diff --git a/uv.lock b/uv.lock index afcfb7d..b09118d 100644 --- a/uv.lock +++ b/uv.lock @@ -244,6 +244,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.15.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/45/78dbf9604ee5b3db24efbf26bed1cb58862fb40480cba821963c69348751/coverage-7.15.3.tar.gz", hash = "sha256:ae7ea5a4614acf399ef0483c4cb34f8f8f01df848d8fcbe7d3ce0865733f1c4d", size = 935592, upload-time = "2026-08-02T18:50:17.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/9c/c8a3a923c24f631695cea2d5e2f02e776bc0af6e03800626e13a6c05a615/coverage-7.15.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5f3f854ab4599d98f7799ac9b91e34e8ec9ebc9a6372ee8c1f3413a68cc8b5e9", size = 222328, upload-time = "2026-08-02T18:47:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/92/51/dda77f34cbd2513d6ffb898c901d19e9ca55f48c0cbc4a1eb173a97d157a/coverage-7.15.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75268348fee1f199653b8a846262aec5581c6bb008c4f58824959fb708cc688f", size = 222832, upload-time = "2026-08-02T18:47:51.219Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/e0faafc4c6e23bd76c76148875ee9ec5781b8f1cd62cea2bc4ca0f0f0e5d/coverage-7.15.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21081739f6264cc594cad2d42b62befbd17633824022866c68720eb0c4b8d6b4", size = 253250, upload-time = "2026-08-02T18:47:52.737Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/4b1e0eeb727ffb471e411c1bd3402184b5dd54a77a762b0e55e87cdf9ae3/coverage-7.15.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:718d366251b060c10731c7dd359de6caea72250036eb94576aa56dacbf830a11", size = 255160, upload-time = "2026-08-02T18:47:54.404Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/a602d2d48f9db9f795e578a86aa914f7b20008e9330902defcfb73d17b3a/coverage-7.15.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa1bbaa502a6e877f3ee67cbac3eba2bb637f623e454e6c37b81b38896dbd48f", size = 257269, upload-time = "2026-08-02T18:47:56.157Z" }, + { url = "https://files.pythonhosted.org/packages/22/fa/bf6db13df2fcee00d2671849fe58c99232ee79a01fec7478c2bf7839b9e1/coverage-7.15.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:494880c9e60782610683f4eb9b65cce4f886673596b8f3cb2dfa079fc551c743", size = 259231, upload-time = "2026-08-02T18:47:57.76Z" }, + { url = "https://files.pythonhosted.org/packages/89/37/8118f13b17fa7d9a3aa2c301d93f2d5ffeef70fa7e27e639a74bdacd3fea/coverage-7.15.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3db264ea689f9e8f9fa4fb9005fee4048c3bff4a547f4cfa27f5086cb0804ec0", size = 253357, upload-time = "2026-08-02T18:47:59.261Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/c7b94fb03962f4d6f0fe13d01c4eb9c4c6e2e714a20d074516ec7582b110/coverage-7.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4e869d4799674d67778e76ddbe2e26cf1673369262e231a8ec259421b1015fea", size = 254961, upload-time = "2026-08-02T18:48:00.901Z" }, + { url = "https://files.pythonhosted.org/packages/87/f9/fe0bd415fa56e36b62b649017c8fc98330858be4c7593789efb78cd24178/coverage-7.15.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:696fc7a28bbf717aba8d2c6963d26702945c7832cb313ba3b323aa5b1afb3156", size = 253024, upload-time = "2026-08-02T18:48:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7c/ffa53506d63ba8a77f5b9557dd6f5a5a5ad85adc680d7857410138f82bd9/coverage-7.15.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3fe9be1c527497d047f770d88a0110189714c36383bb88384508f750c302bffa", size = 256792, upload-time = "2026-08-02T18:48:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c6/df42458e72c18a49fe87e40ccd3fb0314210915256cf4a5593e1b3250e04/coverage-7.15.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2400591f4b2e33746c70846388f8bb4c7e33b820e31cb8c6cb2f25305310438b", size = 252744, upload-time = "2026-08-02T18:48:06.154Z" }, + { url = "https://files.pythonhosted.org/packages/f1/14/8bf18a4b10a44f8ba5f604b00e102f37daf49d581d66a37dc33fa267e1a6/coverage-7.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e557178799282269412a672e5753f2179edfe1b3f0f19b0c98f8e72d482326a", size = 253652, upload-time = "2026-08-02T18:48:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/e530c9bb94e4155817cbd149034105b062a6913bc356ae08f454d155de53/coverage-7.15.3-cp311-cp311-win32.whl", hash = "sha256:68ea6c947375982ae907e19e9d2ef156bd6e68e11f3566dd568d7f4ec974e715", size = 224428, upload-time = "2026-08-02T18:48:09.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/98/0050c692d120988f1973a15196f52dee4ae221848b760281461a2005b613/coverage-7.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:28743dad31622e8c474b17446118037361f5b1f4f2ecdf72d4f6fde246d64446", size = 224906, upload-time = "2026-08-02T18:48:11.611Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ae/c0ef3e2ba3f35fc1c6985811a40edd9331e5b8978c9ecf84699de3edacbe/coverage-7.15.3-cp311-cp311-win_arm64.whl", hash = "sha256:c4398918c4fda32718191239e451fd86ac5ad1e8979b592f1921ee2d1f038965", size = 224448, upload-time = "2026-08-02T18:48:13.304Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6c/bac99d9d4c6abe856e93bf3f5212982ac0bfac126dd4a042753bd53bc5af/coverage-7.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:79a3e32e83227d83d9684459ed579769b56c369ac2d7313099b2d9e031d2e10f", size = 222499, upload-time = "2026-08-02T18:48:15.018Z" }, + { url = "https://files.pythonhosted.org/packages/aa/bc/cb9a39b083bc1aa70586482dab25c9be20bab0ec6c155340e50d9066bb1e/coverage-7.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:767feb87c5886d781d0a69fafd450a20826ddab7b79bce1665deb64d21441b60", size = 222866, upload-time = "2026-08-02T18:48:16.884Z" }, + { url = "https://files.pythonhosted.org/packages/58/fb/beaa453d62000a0a5b39838bee2a137afe609a50a71f55e83c73461e513b/coverage-7.15.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50951e37033c40548d777b8a8454a2cd622dba1136780065678dccaec307c47f", size = 254367, upload-time = "2026-08-02T18:48:18.507Z" }, + { url = "https://files.pythonhosted.org/packages/66/64/43e72500ed6815cef189f9193f29d7af4b078830337c95ea976cd0c0d427/coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:63a4ff67364afb2cac826b8bbd78a5c50ce656a7b7137436b44d7b96a9271088", size = 257103, upload-time = "2026-08-02T18:48:20.172Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/2893e2937adfe02f45fd38e4a8a0a0d8b7a02ff9e012ac3d009bee3c4f16/coverage-7.15.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95e42856509675fe26560310313a6117640e96f9a1e19bb3d220116a27c94c", size = 258220, upload-time = "2026-08-02T18:48:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/30/b4/d5e6e2eb1a62961083734291304b1f85df72e2abe95c76eb88a7f472afd0/coverage-7.15.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abad631cba27094b4631993f4c72e89ac0ca1b3a0236c7abaf8ca79aea619851", size = 260481, upload-time = "2026-08-02T18:48:23.682Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/9b72c5c6a9798a9a12cf65f66e077cc1fdd396e61915c862688f9afe1cae/coverage-7.15.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b0807f1f051dd82a234ad6acdb6f1425baede60be1e84e862496c8cc9262ab9", size = 254749, upload-time = "2026-08-02T18:48:25.32Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/e1c2f759e2dbce559ba85c40c0e4acfecc6cff4b740c294c88e41ccc6111/coverage-7.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8d6df7aeb5bc464040bbc9ae173d875785d3677ebc4307817997d622d74225e", size = 256138, upload-time = "2026-08-02T18:48:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ab/48cc7e760f769e86ae290a125ea6e7209dfbdbbbb7ff4f5d9d1ee7a45d57/coverage-7.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:974471c506c9f5758808b47c1ebf7949ecd0848f5c1020e78675fefe5ff46866", size = 254283, upload-time = "2026-08-02T18:48:29.082Z" }, + { url = "https://files.pythonhosted.org/packages/15/26/39529a68154f99b3a1829debd8b25eac384effeec890a293b5bbdcb49186/coverage-7.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5cba0c9c13e35c86df7998f1afaf6b1da224a3a39e4da59bdabf60c148046dcb", size = 258352, upload-time = "2026-08-02T18:48:30.892Z" }, + { url = "https://files.pythonhosted.org/packages/91/2f/55b82aa3d8d7dd8023a56e7c5c2a70e39a3c44b3353c6cf3faec9ad51566/coverage-7.15.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4d608dc36a364dce33acbf4fc3a50f9d2054c945f233bb0a2cdb4b90bfa17646", size = 253852, upload-time = "2026-08-02T18:48:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/839f4045124cd3518ecf2c58967e58a911202834e7c5a03cfdf2ab0b29f6/coverage-7.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2395869280554a1941da904423c12660c39f721315e1c02d076a7fe0971382f0", size = 255725, upload-time = "2026-08-02T18:48:34.848Z" }, + { url = "https://files.pythonhosted.org/packages/75/21/d25e3e2a9e327798078c877f469dfb6def860bf6e25036529046227d3e15/coverage-7.15.3-cp312-cp312-win32.whl", hash = "sha256:24f3b21840c3eb76cef3cc70b2bf6649010c64471a84a446538a39306e1ba04d", size = 224566, upload-time = "2026-08-02T18:48:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/df90cc1e8d095ce263968a93e04829821b2afb31ac2752c06a2e0a8e3c13/coverage-7.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:fa7b17902c3c1dd8a7adb52679b7f6340bba08443d710c8838e04db8cf62be2a", size = 225098, upload-time = "2026-08-02T18:48:38.941Z" }, + { url = "https://files.pythonhosted.org/packages/65/c7/ec49e43c58967a07163e2d1c6bbd58112b825b2772ab66784afd6a5400ba/coverage-7.15.3-cp312-cp312-win_arm64.whl", hash = "sha256:fcbe83fb7258eacd293bf5322d88807acb35ed12a5cfa99dd8215c083e3b0235", size = 224485, upload-time = "2026-08-02T18:48:40.682Z" }, + { url = "https://files.pythonhosted.org/packages/37/e7/7069b3d6c018917f49ba2e1c5fb910e498c7fefa3a1b78cb1b79e61ff45d/coverage-7.15.3-py3-none-any.whl", hash = "sha256:da78fa6fc7dafe4212839173133ee85afcf42c5cd5f3e47fa7c1c210453b445e", size = 214297, upload-time = "2026-08-02T18:50:14.709Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "cssselect2" version = "0.8.0" @@ -308,13 +352,13 @@ dev = [ { name = "black" }, { name = "ipython" }, { name = "pytest" }, + { name = "pytest-cov" }, { name = "ruff" }, { name = "setuptools-scm" }, ] doc = [ { name = "mkdocs" }, { name = "mkdocs-material", extra = ["imaging"] }, - { name = "mkdocstrings-python" }, ] [package.metadata] @@ -324,15 +368,15 @@ requires-dist = [ { name = "black", marker = "extra == 'dev'" }, { name = "ipython", marker = "extra == 'dev'" }, { name = "loguru" }, - { name = "mkdocs", marker = "extra == 'doc'" }, + { name = "mkdocs", marker = "extra == 'doc'", specifier = "<2" }, { name = "mkdocs-material", extras = ["imaging"], marker = "extra == 'doc'" }, - { name = "mkdocstrings-python", marker = "extra == 'doc'" }, { name = "numpy", specifier = ">=2.0" }, { name = "openpyxl" }, { name = "pandas" }, { name = "psycopg2-binary" }, { name = "pyarrow" }, { name = "pytest", marker = "extra == 'dev'" }, + { name = "pytest-cov", marker = "extra == 'dev'" }, { name = "requests" }, { name = "ruff", marker = "extra == 'dev'" }, { name = "setuptools-scm", marker = "extra == 'dev'", specifier = ">=8.0" }, @@ -374,7 +418,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, - { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, @@ -382,25 +425,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, ] -[[package]] -name = "griffe" -version = "1.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, -] - [[package]] name = "idna" version = "3.11" @@ -607,20 +637,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] -[[package]] -name = "mkdocs-autorefs" -version = "1.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, -] - [[package]] name = "mkdocs-get-deps" version = "0.2.0" @@ -672,37 +688,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, ] -[[package]] -name = "mkdocstrings" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, - { name = "mkdocs-autorefs" }, - { name = "pymdown-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086, upload-time = "2025-11-27T15:39:40.534Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135, upload-time = "2025-11-27T15:39:39.301Z" }, -] - -[[package]] -name = "mkdocstrings-python" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "griffe" }, - { name = "mkdocs-autorefs" }, - { name = "mkdocstrings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, -] - [[package]] name = "mypy-extensions" version = "1.1.0" @@ -1039,6 +1024,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1275,6 +1274,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, ] +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + [[package]] name = "traitlets" version = "5.14.3" From 87c3459340cca218ce9689263f222fa37e769c77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 04:54:07 +0000 Subject: [PATCH 2/2] chore: update ER diagrams [skip ci] --- diagrams/erdiagram_targetdb_latest.pdf | Bin 47887 -> 47907 bytes docs/tbls/public.fluxstd.md | 2 +- docs/tbls/public.sky.md | 2 +- docs/tbls/public.target.md | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/diagrams/erdiagram_targetdb_latest.pdf b/diagrams/erdiagram_targetdb_latest.pdf index b3a2300b0e480a6f2f9afe8f786aaa7e7d015bce..0091ba30257568fa299bc88626e8ff20a2397b0e 100644 GIT binary patch delta 31297 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 IB}Gq03fm*#!2kdN delta 31249 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 diff --git a/docs/tbls/public.fluxstd.md b/docs/tbls/public.fluxstd.md index 7e61766..3ca7a85 100644 --- a/docs/tbls/public.fluxstd.md +++ b/docs/tbls/public.fluxstd.md @@ -102,9 +102,9 @@ | fluxstd_pkey | CREATE UNIQUE INDEX fluxstd_pkey ON public.fluxstd USING btree (fluxstd_id) | | uq_obj_id_input_catalog_id_version | CREATE UNIQUE INDEX uq_obj_id_input_catalog_id_version ON public.fluxstd USING btree (obj_id, input_catalog_id, version) | | fluxstd_q3c_ang2ipix_idx | CREATE INDEX fluxstd_q3c_ang2ipix_idx ON public.fluxstd USING btree (q3c_ang2ipix(ra, "dec")) | +| ix_fluxstd_version | CREATE INDEX ix_fluxstd_version ON public.fluxstd USING btree (version) | | ix_fluxstd_input_catalog_fluxstdid | CREATE INDEX ix_fluxstd_input_catalog_fluxstdid ON public.fluxstd USING btree (input_catalog_id, fluxstd_id) | | ix_fluxstd_version_fluxstdid | CREATE INDEX ix_fluxstd_version_fluxstdid ON public.fluxstd USING btree (version, fluxstd_id) | -| ix_fluxstd_version | CREATE INDEX ix_fluxstd_version ON public.fluxstd USING btree (version) | ## Relations diff --git a/docs/tbls/public.sky.md b/docs/tbls/public.sky.md index 6b4f98d..f4d93bf 100644 --- a/docs/tbls/public.sky.md +++ b/docs/tbls/public.sky.md @@ -34,9 +34,9 @@ | ---- | ---------- | | sky_pkey | CREATE UNIQUE INDEX sky_pkey ON public.sky USING btree (sky_id) | | sky_obj_id_input_catalog_id_version_key | CREATE UNIQUE INDEX sky_obj_id_input_catalog_id_version_key ON public.sky USING btree (obj_id, input_catalog_id, version) | -| ix_sky_input_catalog_id | CREATE INDEX ix_sky_input_catalog_id ON public.sky USING btree (input_catalog_id) | | sky_q3c_ang2ipix_idx | CREATE INDEX sky_q3c_ang2ipix_idx ON public.sky USING btree (q3c_ang2ipix(ra, "dec")) | | ix_sky_version | CREATE INDEX ix_sky_version ON public.sky USING btree (version) | +| ix_sky_input_catalog_id | CREATE INDEX ix_sky_input_catalog_id ON public.sky USING btree (input_catalog_id) | ## Relations diff --git a/docs/tbls/public.target.md b/docs/tbls/public.target.md index 12ceb5a..1728dae 100644 --- a/docs/tbls/public.target.md +++ b/docs/tbls/public.target.md @@ -121,11 +121,11 @@ | target_pkey | CREATE UNIQUE INDEX target_pkey ON public.target USING btree (target_id) | | target_propid_obcode_catid_objid_resolution_key | CREATE UNIQUE INDEX target_propid_obcode_catid_objid_resolution_key ON public.target USING btree (proposal_id, ob_code, input_catalog_id, obj_id, is_medium_resolution) | | target_propid_obcode_key | CREATE UNIQUE INDEX target_propid_obcode_key ON public.target USING btree (proposal_id, ob_code) | -| target_proposal_id_idx | CREATE INDEX target_proposal_id_idx ON public.target USING btree (proposal_id) | -| target_obj_id_input_catalog_id_idx | CREATE INDEX target_obj_id_input_catalog_id_idx ON public.target USING btree (obj_id, input_catalog_id) | | target_input_catalog_id_idx | CREATE INDEX target_input_catalog_id_idx ON public.target USING btree (input_catalog_id) | | target_proposal_id_obj_id_idx | CREATE INDEX target_proposal_id_obj_id_idx ON public.target USING btree (proposal_id, obj_id) | | target_q3c_ang2ipix_idx | CREATE INDEX target_q3c_ang2ipix_idx ON public.target USING btree (q3c_ang2ipix(ra, "dec")) | +| target_proposal_id_idx | CREATE INDEX target_proposal_id_idx ON public.target USING btree (proposal_id) | +| target_obj_id_input_catalog_id_idx | CREATE INDEX target_obj_id_input_catalog_id_idx ON public.target USING btree (obj_id, input_catalog_id) | ## Relations