Source code for interpretune.utils.neuronpedia_db_utils

# pyright: reportMissingTypeStubs=false
from __future__ import annotations

import os
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse, urlunparse
from urllib.request import Request, urlopen

# neuronpedia_utils is a local-checkout package (neuronpedia repo, utils/neuronpedia-utils;
# install editable with --no-deps) and is absent in plain CI / fresh envs. The typed structural
# stand-ins in _neuronpedia_utils_fallbacks are imported UNCONDITIONALLY (always-resolvable
# declarations, so this module — and interpretune.utils, which re-exports from it — stays importable
# and statically analyzable everywhere); the real package then rebinds them at runtime when present.
# Fallback functional entry points raise with an actionable install message at call time.
from interpretune.utils._neuronpedia_utils_fallbacks import (
    DEFAULT_COLUMNAR_COPY_IMPORT_TABLES,
    DEFAULT_COLUMNAR_IMPORT_TABLES,
    DEFAULT_IMPORT_MODE_CONFIGS,
    NeuronpediaBundleImportParity,
    NeuronpediaBundleSummaryParity,
    NeuronpediaExportBundleSummary,
    NeuronpediaLocalDBImportError,
    NeuronpediaLocalImportSummary,
    benchmark_neuronpedia_export_bundle_local_db_modes as _benchmark_neuronpedia_export_bundle_local_db_modes,
    compare_neuronpedia_export_bundle_summaries,
    compare_neuronpedia_export_bundle_to_import_summary,
    summarize_neuronpedia_export_bundle,
    summarize_neuronpedia_export_bundle_arrow,
    summarize_neuronpedia_export_bundle_parquet,
)
from interpretune.utils._neuronpedia_utils_fallbacks import (
    import_saedashboard_columnar_bundle_local_db as _import_saedashboard_columnar_bundle_local_db,
    import_neuronpedia_export_bundle_local_db as _import_neuronpedia_export_bundle_local_db,
)
from interpretune.utils.import_utils import _NEURONPEDIA_UTILS_AVAILABLE

# `not TYPE_CHECKING` keeps the runtime rebind invisible to pyright: static analysis always
# checks against the resolvable typed fallbacks above, while runtime uses the real package
# whenever it is installed.
if not TYPE_CHECKING and _NEURONPEDIA_UTILS_AVAILABLE:
    from neuronpedia_utils.local_db_import import (  # type: ignore[import-untyped] # noqa: F811
        DEFAULT_COLUMNAR_COPY_IMPORT_TABLES,
        DEFAULT_COLUMNAR_IMPORT_TABLES,
        DEFAULT_IMPORT_MODE_CONFIGS,
        NeuronpediaBundleImportParity,
        NeuronpediaBundleSummaryParity,
        NeuronpediaExportBundleSummary,
        NeuronpediaLocalDBImportError,
        NeuronpediaLocalImportSummary,
        benchmark_neuronpedia_export_bundle_local_db_modes as _benchmark_neuronpedia_export_bundle_local_db_modes,
        compare_neuronpedia_export_bundle_summaries,
        compare_neuronpedia_export_bundle_to_import_summary,
        summarize_neuronpedia_export_bundle,
        summarize_neuronpedia_export_bundle_arrow,
        summarize_neuronpedia_export_bundle_parquet,
    )
    from neuronpedia_utils.local_db_import import (  # type: ignore[import-untyped] # noqa: F811
        import_saedashboard_columnar_bundle_local_db as _import_saedashboard_columnar_bundle_local_db,
        import_neuronpedia_export_bundle_local_db as _import_neuronpedia_export_bundle_local_db,
    )

DEFAULT_LOCAL_NEURONPEDIA_WEBAPP_URL = os.getenv("LOCAL_NEURONPEDIA_WEBAPP_URL", "http://localhost:3000")
DEFAULT_LOCAL_NEURONPEDIA_DB_ENV_VARS = (
    "LOCAL_NEURONPEDIA_DB_URL",
    "POSTGRES_URL_NON_POOLING",
    "DATABASE_URL",
)
DEFAULT_LOCAL_NEURONPEDIA_DOCKER_HOSTNAME = "postgres"
DEFAULT_LOCAL_NEURONPEDIA_HOST = "127.0.0.1"
DEFAULT_LOCAL_NEURONPEDIA_POSTGRES_PORT_ENV = "POSTGRES_HOST_PORT"
DEFAULT_LOCAL_NEURONPEDIA_DB_TIMEOUT_SECONDS = 5

NeuronpediaDBError = NeuronpediaLocalDBImportError

__all__ = [
    "DEFAULT_COLUMNAR_COPY_IMPORT_TABLES",
    "DEFAULT_COLUMNAR_IMPORT_TABLES",
    "DEFAULT_IMPORT_MODE_CONFIGS",
    "DEFAULT_LOCAL_NEURONPEDIA_WEBAPP_URL",
    "LocalNeuronpediaServiceStatus",
    "NeuronpediaBundleImportParity",
    "NeuronpediaBundleSummaryParity",
    "NeuronpediaDBError",
    "NeuronpediaExportBundleSummary",
    "NeuronpediaLocalImportSummary",
    "benchmark_neuronpedia_export_bundle_local_db_modes",
    "check_local_neuronpedia_services",
    "compare_neuronpedia_export_bundle_summaries",
    "compare_neuronpedia_export_bundle_to_import_summary",
    "import_neuronpedia_export_bundle_local_db",
    "import_saedashboard_columnar_bundle_local_db",
    "resolve_local_neuronpedia_db_url",
    "rewrite_container_postgres_url_for_host",
    "summarize_neuronpedia_export_bundle",
    "summarize_neuronpedia_export_bundle_arrow",
    "summarize_neuronpedia_export_bundle_parquet",
]


[docs] @dataclass(frozen=True) class LocalNeuronpediaServiceStatus: """Availability snapshot for the local Neuronpedia webapp and Postgres services.""" webapp_url: str webapp_available: bool webapp_status_code: int | None webapp_error: str | None db_url_redacted: str | None db_available: bool db_error: str | None
def _redact_connection_url(connection_url: str) -> str: parsed = urlparse(connection_url) if parsed.password is None: return connection_url username = parsed.username or "" netloc = parsed.netloc.replace(f":{parsed.password}@", ":***@", 1) if username and not netloc.startswith(username): netloc = f"{username}:***@{parsed.hostname or ''}" if parsed.port is not None: netloc += f":{parsed.port}" return urlunparse(parsed._replace(netloc=netloc))
[docs] def rewrite_container_postgres_url_for_host( connection_url: str, *, env: Mapping[str, str] | None = None, container_hostname: str = DEFAULT_LOCAL_NEURONPEDIA_DOCKER_HOSTNAME, host: str = DEFAULT_LOCAL_NEURONPEDIA_HOST, port_env_var: str = DEFAULT_LOCAL_NEURONPEDIA_POSTGRES_PORT_ENV, ) -> str: """Rewrite a docker-only Postgres URL to the host-mapped local port when configured.""" env_map = dict(os.environ if env is None else env) parsed = urlparse(connection_url) if parsed.hostname != container_hostname: return connection_url host_port = env_map.get(port_env_var) if not host_port: return connection_url username = parsed.username or "" password = parsed.password or "" auth_prefix = username if password: auth_prefix = f"{auth_prefix}:{password}" if auth_prefix: auth_prefix = f"{auth_prefix}@" netloc = f"{auth_prefix}{host}:{host_port}" return urlunparse(parsed._replace(netloc=netloc))
[docs] def resolve_local_neuronpedia_db_url( local_db_url: str | None = None, *, env: Mapping[str, str] | None = None, ) -> str: """Resolve the best local Neuronpedia Postgres URL from explicit input or environment.""" env_map = dict(os.environ if env is None else env) candidate = local_db_url if not candidate: for env_var in DEFAULT_LOCAL_NEURONPEDIA_DB_ENV_VARS: candidate = env_map.get(env_var) if candidate: break if not candidate: raise NeuronpediaDBError( "Could not resolve a local Neuronpedia DB URL. Set LOCAL_NEURONPEDIA_DB_URL or POSTGRES_URL_NON_POOLING." ) return rewrite_container_postgres_url_for_host(candidate, env=env_map)
[docs] def check_local_neuronpedia_services( *, local_db_url: str | None = None, webapp_url: str = DEFAULT_LOCAL_NEURONPEDIA_WEBAPP_URL, timeout_seconds: int = DEFAULT_LOCAL_NEURONPEDIA_DB_TIMEOUT_SECONDS, ) -> LocalNeuronpediaServiceStatus: """Probe the local Neuronpedia webapp and Postgres services without raising on failure.""" import psycopg resolved_db_url: str | None = None db_url_redacted: str | None = None db_available = False db_error: str | None = None try: resolved_db_url = resolve_local_neuronpedia_db_url(local_db_url) db_url_redacted = _redact_connection_url(resolved_db_url) with psycopg.connect(resolved_db_url, connect_timeout=timeout_seconds) as connection: with connection.cursor() as cursor: cursor.execute("SELECT 1") cursor.fetchone() db_available = True except Exception as exc: # pragma: no cover - exercised in integration contexts db_error = str(exc) webapp_available = False webapp_status_code: int | None = None webapp_error: str | None = None try: request = Request(webapp_url, method="GET") with urlopen(request, timeout=timeout_seconds) as response: webapp_status_code = getattr(response, "status", None) webapp_available = webapp_status_code is not None and 200 <= webapp_status_code < 500 except Exception as exc: # pragma: no cover - exercised in integration contexts webapp_error = str(exc) return LocalNeuronpediaServiceStatus( webapp_url=webapp_url, webapp_available=webapp_available, webapp_status_code=webapp_status_code, webapp_error=webapp_error, db_url_redacted=db_url_redacted, db_available=db_available, db_error=db_error, )
[docs] def import_neuronpedia_export_bundle_local_db( export_root: Path | str, *, local_db_url: str | None = None, prefer_arrow_for_tables: Iterable[str] = (), prefer_copy_for_tables: Iterable[str] = (), artifact_format_by_table: dict[str, str] | None = None, ) -> NeuronpediaLocalImportSummary: """Import a Neuronpedia export bundle through the Neuronpedia-owned importer.""" return _import_neuronpedia_export_bundle_local_db( export_root, local_db_url=resolve_local_neuronpedia_db_url(local_db_url), prefer_arrow_for_tables=prefer_arrow_for_tables, prefer_copy_for_tables=prefer_copy_for_tables, artifact_format_by_table=artifact_format_by_table, )
def import_saedashboard_columnar_bundle_local_db( columnar_root: Path | str, *, local_db_url: str | None = None, model_id: str, source_set_name: str, source_id: str, creator_id: str, decode_token_ids: Any, created_at: Any = None, activation_id_prefix: str = "columnar-activation", pad_token_id: int | None = None, hook_name: str | None = None, chunk_size: int = 65000, **metadata_kwargs: Any, ) -> NeuronpediaLocalImportSummary: """Import SAEDashboard columnar artifacts through the Neuronpedia-owned importer.""" return _import_saedashboard_columnar_bundle_local_db( columnar_root, local_db_url=resolve_local_neuronpedia_db_url(local_db_url), model_id=model_id, source_set_name=source_set_name, source_id=source_id, creator_id=creator_id, decode_token_ids=decode_token_ids, created_at=created_at, activation_id_prefix=activation_id_prefix, pad_token_id=pad_token_id, hook_name=hook_name, chunk_size=chunk_size, **metadata_kwargs, ) def benchmark_neuronpedia_export_bundle_local_db_modes( export_root: Path | str, *, local_db_url: str | None = None, import_modes: dict[str, dict[str, Any]] | None = None, rollback_each_mode: bool = True, ) -> dict[str, NeuronpediaLocalImportSummary]: """Benchmark Neuronpedia-owned local import modes after Interpretune URL resolution.""" return _benchmark_neuronpedia_export_bundle_local_db_modes( export_root, local_db_url=resolve_local_neuronpedia_db_url(local_db_url), import_modes=import_modes, rollback_each_mode=rollback_each_mode, )