Source code for flpq_data.dataset.reachable_pairs

"""Reference reachable-pair counts for graph x grammar pairs."""

import csv
import logging
import pathlib
import shutil
from typing import Optional

import requests

from flpq_data.config import DATASET_VERSION
from flpq_data.dataset.cache import version_dir

__all__ = [
    "REACHABLE_PAIRS_CSV",
    "REACHABLE_PAIRS_KEY_PREFIX",
    "REACHABLE_PAIRS_URL",
    "download_reachable_pairs",
    "reachable_pairs",
]

REACHABLE_PAIRS_FILENAME = "reachable_pairs.csv"

#: Version prefix the table is published under on the dataset object storage.
#: Derived from :data:`flpq_data.config.DATASET_VERSION`, so the URL follows
#: the package version.
REACHABLE_PAIRS_KEY_PREFIX: str = DATASET_VERSION

#: Public URL of the versioned reachable-pairs CSV (see
#: :func:`download_reachable_pairs`).
REACHABLE_PAIRS_URL: str = (
    f"https://cfpq-data.storage.yandexcloud.net/"
    f"{REACHABLE_PAIRS_KEY_PREFIX}/{REACHABLE_PAIRS_FILENAME}"
)

#: Path to the reference CSV shipped with the package; always available
#: offline. :func:`download_reachable_pairs` fetches the versioned copy from
#: the dataset storage instead, and :func:`reachable_pairs` prefers it when
#: present.
REACHABLE_PAIRS_CSV: pathlib.Path = (
    pathlib.Path(__file__).parent / REACHABLE_PAIRS_FILENAME
)


[docs] def download_reachable_pairs() -> pathlib.Path: """Download the versioned reachable-pairs CSV from the dataset storage. The table is also shipped with the package (``REACHABLE_PAIRS_CSV``); this fetches the copy published under the current version prefix (``REACHABLE_PAIRS_URL``) into the machine-global cache (``version_dir() / "reachable_pairs.csv"``), so an updated table can be consumed without reinstalling. Once downloaded, :func:`reachable_pairs` reads it instead of the bundled copy. Returns ------- path : pathlib.Path Path to the downloaded CSV. Raises ------ requests.HTTPError If the table is not available at ``REACHABLE_PAIRS_URL``. """ destination = version_dir() / REACHABLE_PAIRS_FILENAME destination.parent.mkdir(exist_ok=True, parents=True) logging.info(f"Downloading reachable pairs CSV from {REACHABLE_PAIRS_URL}") with requests.get(url=REACHABLE_PAIRS_URL, stream=True) as response: response.raise_for_status() with open(destination, "wb") as file: shutil.copyfileobj(response.raw, file) logging.info(f"Downloaded reachable pairs CSV to {destination}") return destination
def _csv_path() -> pathlib.Path: """Returns the CSV to read: the per-version cache copy if present, else bundled.""" cached = version_dir() / REACHABLE_PAIRS_FILENAME return cached if cached.exists() else REACHABLE_PAIRS_CSV
[docs] def reachable_pairs( graph: Optional[str] = None, grammar: Optional[str] = None, category: Optional[str] = None, query_class: Optional[str] = None, ) -> list[dict]: """Return reference reachable-pair counts. Each row is a dict with keys ``graph``, ``grammar``, ``category``, ``query_class``, and ``num_reachable_pairs`` (int or None when not yet available). Parameters ---------- graph: Filter by graph name (exact match). grammar: Filter by grammar file name (exact match, e.g. ``"c_alias.cnf"``). category: Filter by graph category (exact match, e.g. ``"rdf"``); the categories are the sections of the Graphs catalog on the site. query_class: Filter by query class (exact match, one of ``"cfpq"``, ``"rpq"``, ``"mcfpq"`` — the names of the ``queries/<class>/`` directories in the graph archives). """ rows: list[dict] = [] with _csv_path().open(newline="") as f: for row in csv.DictReader(f): if graph is not None and row["graph"] != graph: continue if grammar is not None and row["grammar"] != grammar: continue if category is not None and row["category"] != category: continue if query_class is not None and row["query_class"] != query_class: continue val = row["num_reachable_pairs"] rows.append( { "graph": row["graph"], "grammar": row["grammar"], "category": row["category"], "query_class": row["query_class"], "num_reachable_pairs": int(val) if val else None, } ) return rows