diff --git a/.gitignore b/.gitignore index 911468e..cb3bf5b 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ uuid_mapping_*.csv reaction_connections_*.csv decomposed_uid_mapping_*.csv best_matches_*.csv +graphify-out/ diff --git a/bin/backfill-proxy-mapping.py b/bin/backfill-proxy-mapping.py new file mode 100644 index 0000000..cd98f01 --- /dev/null +++ b/bin/backfill-proxy-mapping.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Backfill entity_reaction_proxy_mapping.csv for already-generated pathways. + +Regenerating a network from scratch is expensive; this reuses the existing +logic_network.csv + stid_to_uuid_mapping.csv and only adds the new proxy file +(which just needs Neo4j + those two artifacts). Going forward the generator +emits the file itself; this is a one-time catch-up for the existing catalog. +""" +import os +import sys +from pathlib import Path + +import pandas as pd + +# Allow running as `bin/backfill-proxy-mapping.py` — put the repo root on the +# path so `src` imports resolve regardless of cwd. +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from src.neo4j_connector import prefetch_entity_data # noqa: E402 +from src.logic_network_generator import export_entity_reaction_proxy_mapping # noqa: E402 + +OUTPUT = Path(__file__).resolve().parent.parent / "output" + + +def backfill(pathway_dir: Path) -> None: + name = pathway_dir.name + if not name.endswith(tuple(f"R-HSA-{n}" for n in [""])) and "R-HSA-" not in name: + return + pid = "R-HSA-" + name.rsplit("R-HSA-", 1)[-1] + net_f = pathway_dir / "logic_network.csv" + map_f = pathway_dir / "stid_to_uuid_mapping.csv" + rc_f = pathway_dir / "cache" / "reaction_connections.csv" + if not (net_f.exists() and map_f.exists()): + print(f" skip {name}: missing artifacts") + return + + network = pd.read_csv(net_f) + mapping = pd.read_csv(map_f) + reactome_id_to_uuid = dict(zip(mapping["uuid"].astype(str), + mapping["stable_id"].astype(str))) + # Approximate reaction_id_map from the mapping (reaction stIds carry UUIDs + # too); only reaction stIds get queried, so entity rows are harmless. + rid_map = mapping.rename(columns={"uuid": "uid", "stable_id": "reactome_id"}) + + # Warm caches with the pathway's reactions for fast terminal-component walks. + if rc_f.exists(): + rc = pd.read_csv(rc_f, dtype=str) + rxn_ids = set(rc["preceding_reaction_id"].dropna()) | \ + set(rc["following_reaction_id"].dropna()) + if rxn_ids: + prefetch_entity_data(list(rxn_ids)) + + out_f = pathway_dir / "entity_reaction_proxy_mapping.csv" + export_entity_reaction_proxy_mapping( + network, rid_map, reactome_id_to_uuid, pid, str(out_f)) + df = pd.read_csv(out_f) + print(f" {name}: {len(df)} rows, " + f"{df['entity_stable_id'].nunique() if len(df) else 0} entities") + + +if __name__ == "__main__": + targets = sys.argv[1:] + if targets: + dirs = [OUTPUT / t for t in targets] + else: + dirs = [d for d in sorted(OUTPUT.iterdir()) if d.is_dir()] + for d in dirs: + try: + backfill(d) + except Exception as e: + print(f" ERROR {d.name}: {e}") diff --git a/bin/create-pathways.py b/bin/create-pathways.py index a8703a3..504abb1 100755 --- a/bin/create-pathways.py +++ b/bin/create-pathways.py @@ -3,6 +3,17 @@ import os import sys + +# Determinism: the generator's output depends on set/dict iteration order, which +# Python randomizes per-process via hash seeding — so regenerating a pathway +# yields structurally different networks run-to-run (~5.8% of edges for TP53). +# Pin the hash seed so generation is reproducible. PYTHONHASHSEED must be set +# before the interpreter starts, so re-exec once if it isn't already fixed. +# Override with LNG_ALLOW_NONDETERMINISM=1. See reactome/logic-network-generator#42. +if os.environ.get("PYTHONHASHSEED") != "0" and os.environ.get("LNG_ALLOW_NONDETERMINISM") != "1": + os.environ["PYTHONHASHSEED"] = "0" + os.execv(sys.executable, [sys.executable] + sys.argv) + from typing import List, Tuple import pandas as pd diff --git a/bin/validate-against-mpbiopath.py b/bin/validate-against-mpbiopath.py index aed9e27..f3c0498 100644 --- a/bin/validate-against-mpbiopath.py +++ b/bin/validate-against-mpbiopath.py @@ -44,6 +44,27 @@ DOWN, NORMAL, UP = 0, 1, 2 MAX_ITERATIONS = 50 +# AND-combination mode for the propagator. "min" is Boolean MIN (rate-limiting +# reagent wins; can't carry upregulations through gates whose other inputs +# are unperturbed). "signed" sums each input's displacement from NORMAL and +# uses the sign: net up → UP, net down → DOWN, balanced or unperturbed → +# NORMAL. Switch via --propagator on the CLI. +PROPAGATOR_MODE = "min" + + +def signed_and(contribs: list[int]) -> int: + """Sum-of-displacements AND. (state - NORMAL) per input; sign decides output.""" + displacement = sum(c - NORMAL for c in contribs) + if displacement > 0: + return UP + if displacement < 0: + return DOWN + return NORMAL + + +def combine_and(contribs: list[int]) -> int: + return signed_and(contribs) if PROPAGATOR_MODE == "signed" else min(contribs) + def invert(state: int) -> int: return {DOWN: UP, NORMAL: NORMAL, UP: DOWN}[state] @@ -71,6 +92,26 @@ def build_stid_to_uuids(pathway_dir: Path) -> dict[str, list[str]]: return out +def load_entity_reaction_proxies(pathway_dir: Path) -> dict[str, list[str]]: + """entity stable_id → list of proxy reaction UUIDs. + + Curated species (often Complexes containing an EntitySet) get expanded into + virtual variants during generation, so the parent's stId isn't in + stid_to_uuid_mapping.csv. The generator's entity_reaction_proxy_mapping.csv + points each such species at the UUIDs of the reaction that produces it, so + we can read reaction flux as a proxy for the species' state. Absent file → + no proxies (pathway was generated by an older generator). + """ + out: dict[str, list[str]] = defaultdict(list) + f = pathway_dir / "entity_reaction_proxy_mapping.csv" + if not f.exists(): + return out + proxies = pd.read_csv(f) + for _, row in proxies.iterrows(): + out[str(row["entity_stable_id"])].append(str(row["proxy_uuid"])) + return out + + def gene_name_to_stids(graph, gene_names: list[str]) -> dict[str, list[str]]: """Map gene names to all PhysicalEntity stable IDs whose reference entity has that gene name.""" rows = graph.run( @@ -129,9 +170,9 @@ def propagate( or_contribs.append(src_state) if and_contribs and or_contribs: - new_state[uuid_] = max(min(and_contribs), max(or_contribs)) + new_state[uuid_] = max(combine_and(and_contribs), max(or_contribs)) elif and_contribs: - new_state[uuid_] = min(and_contribs) + new_state[uuid_] = combine_and(and_contribs) elif or_contribs: new_state[uuid_] = max(or_contribs) if new_state == state: @@ -254,11 +295,19 @@ def validate_one_pathway( return {"status": "no_curator_file", "name": pathway_name} network = load_network(pathway_dir) stid_to_uuids = build_stid_to_uuids(pathway_dir) + entity_reaction_proxies = load_entity_reaction_proxies(pathway_dir) - # Resolve key outputs (numeric dbId → list of UUIDs) + # Resolve key outputs (numeric dbId → list of UUIDs). Fall back to the + # generator's proxy mapping when the curated species was decomposed into + # virtual variants and isn't directly addressable — read the producing + # reaction's flux as a proxy for the species' state. key_output_uuids: dict[str, list[str]] = {} for ko in curator["key_output"].astype(str): - key_output_uuids[ko] = stid_to_uuids.get(f"R-HSA-{ko}", []) + sid = f"R-HSA-{ko}" + uuids = stid_to_uuids.get(sid, []) + if not uuids: + uuids = entity_reaction_proxies.get(sid, []) + key_output_uuids[ko] = uuids # Resolve perturbation genes perturbations = parse_perturbation_columns(curator) @@ -366,7 +415,14 @@ def main(): "Experimental is only available for 10 pathways and contains -999 (not measured) " "cells which are skipped.", ) + ap.add_argument( + "--propagator", choices=["min", "signed"], default="min", + help="AND-combination rule. 'min' = Boolean MIN (default); 'signed' = sum of " + "displacements from NORMAL, sign decides direction (handles upregulation symmetrically).", + ) args = ap.parse_args() + global PROPAGATOR_MODE + PROPAGATOR_MODE = args.propagator output_root = Path(args.output_dir) pathways = pd.read_csv("/tmp/mpbio_pathways.tsv", sep="\t") diff --git a/bin/validate-logic-network.py b/bin/validate-logic-network.py new file mode 100644 index 0000000..769e0e2 --- /dev/null +++ b/bin/validate-logic-network.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +"""Validate a generated logic-network output dir against the LinkML schema. + +The CSVs are the flat serialization; multivalued slots (member_leaves, +source_sets, chosen_members) are pipe-delimited cells. This loads them back +into the native LinkML data model (real arrays) and validates the assembled +``LogicNetwork`` instance against schema/logic_network.linkml.yaml. + +Usage: + poetry run python bin/validate-logic-network.py +""" +import sys +from pathlib import Path + +import pandas as pd +from linkml.validator import validate + +SCHEMA = Path(__file__).resolve().parent.parent / "schema" / "logic_network.linkml.yaml" +MULTIVALUED = ("member_leaves", "source_sets", "chosen_members") + + +def _split(v): + if v is None or (isinstance(v, float) and pd.isna(v)) or v == "": + return [] + return [p for p in str(v).split("|") if p] + + +def _clean(v): + return None if (v is None or (isinstance(v, float) and pd.isna(v)) or v == "") else v + + +def load_instance(d: Path) -> dict: + nodes = [] + for r in pd.read_csv(d / "nodes.csv", dtype=str, keep_default_na=False).to_dict("records"): + node = {k: _clean(v) for k, v in r.items() if k not in MULTIVALUED} + for k in MULTIVALUED: + node[k] = _split(r.get(k)) + nodes.append({k: v for k, v in node.items() if v is not None or k in MULTIVALUED}) + + contexts = pd.read_csv(d / "node_reaction_context.csv", dtype=str, + keep_default_na=False).to_dict("records") + + edges = [] + for r in pd.read_csv(d / "logic_network.csv", keep_default_na=False).to_dict("records"): + e = {k: _clean(v) for k, v in r.items()} + if e.get("stoichiometry") not in (None, ""): + e["stoichiometry"] = int(float(e["stoichiometry"])) + edges.append({k: v for k, v in e.items() if v is not None}) + + return {"pathway_id": d.name, "nodes": nodes, + "node_reaction_contexts": contexts, "edges": edges} + + +def main() -> int: + if len(sys.argv) != 2: + print(__doc__) + return 2 + d = Path(sys.argv[1]) + report = validate(load_instance(d), str(SCHEMA), "LogicNetwork") + if not report.results: + print(f"VALID: {d} conforms to {SCHEMA.name}") + return 0 + print(f"INVALID: {len(report.results)} problem(s) in {d}") + for res in report.results[:25]: + print(f" [{res.severity}] {res.message}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/poetry.lock b/poetry.lock index 9652f8d..eed5001 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,176 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "alabaster" +version = "1.0.0" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.10" +files = [ + {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, + {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +description = "ANTLR 4.9.3 runtime for Python 3.7" +optional = false +python-versions = "*" +files = [ + {file = "antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b"}, +] + +[[package]] +name = "arrow" +version = "1.4.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205"}, + {file = "arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +tzdata = {version = "*", markers = "python_version >= \"3.9\""} + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2025.2)", "simplejson (==3.*)"] + +[[package]] +name = "attrs" +version = "26.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.9" +files = [ + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, +] + +[[package]] +name = "babel" +version = "2.18.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, + {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, +] + +[package.extras] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] + +[[package]] +name = "backports-zstd" +version = "1.6.0" +description = "Backport of compression.zstd" +optional = false +python-versions = "<3.14,>=3.10" +files = [ + {file = "backports_zstd-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:73000459db113a658c4fb0510100ef0e79137b5828bf957b7709aacae4eb1b87"}, + {file = "backports_zstd-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6e78d5e28f812b39f92397806ecddd4a6f3bf35531a8c039a1f187abc931af8"}, + {file = "backports_zstd-1.6.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:32f04d54ec1fdf3aa648b24a10b1c9234ed2046cc4af7a8850cbc236c05d42f3"}, + {file = "backports_zstd-1.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83415af3c64550a56cc20b4cce59bbaa81f21d28466d7adf98feff011ecbc66d"}, + {file = "backports_zstd-1.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3c17e6a267d13de9cbf14bf2ebfa87e03d26692456fc67d2dbed9da4f479b18"}, + {file = "backports_zstd-1.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:75578c71644b031118ce938855a53530708db7f4af6e83e2f8840d5a1de990f8"}, + {file = "backports_zstd-1.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4ae7ed5a6d813450cc2d818284ea3db9721edcef50a56aae42ea06feec38c6e"}, + {file = "backports_zstd-1.6.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5e9a8370c8ed873083d5de956d6b2e60adbad31e52d7a11111c96ef01d1910ae"}, + {file = "backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2d1ccfe088e8279d605011a3575619a74526c261be357695b3258c0f636115a"}, + {file = "backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e73a550dbeb84e8fa50f8385f7735e9a4735b465851ef617d02f80ab10e44e7e"}, + {file = "backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:84f92e5a60a78c72ccda79d0417d311a1f6da18f446423ed411726d545bf7b56"}, + {file = "backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0eb4281f402b94d397b7482f6d9efd04c28274e4ed6eb57eb1f87bdd091a6a87"}, + {file = "backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d6b9b06323e3ba947c0003b2d70e02f33c90c36bc6262a92eb8201afc4a1aa08"}, + {file = "backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8872a0e9f1af975966b5be6af7eebd3dc4046f15e470b719316516dc3d137cd6"}, + {file = "backports_zstd-1.6.0-cp310-cp310-win32.whl", hash = "sha256:c14fa5dc39a804f1b92d63506f450eca5c59647a18d197d1a564b89dac1be1ce"}, + {file = "backports_zstd-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8219d6fceae6b39535c4ac323dba0923d10f781d59962ff3504e693fdcafa92c"}, + {file = "backports_zstd-1.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:b7bc9a0b66097f03820a54316d2fdd0beb38859cf98f10d63e94c55450ed8920"}, + {file = "backports_zstd-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4fc41b2df5529cad5ceb230319e82728096d4b353ce8d4df68a2ec37e291bb8"}, + {file = "backports_zstd-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:83391ef5935cc0f329b1abca414ae20ffe40d335fc21a4b5e664f08a74317d5f"}, + {file = "backports_zstd-1.6.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:7d3f64c503af7b60115b97c16feaf75bd191ef2c978d5c0c7725a6682bef63c5"}, + {file = "backports_zstd-1.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0308990ffc998df3c7ed35276bde049728b5c3956203cae40d80893576a41459"}, + {file = "backports_zstd-1.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c298785e2fadeab82342040f2d9ce764ce500e6da6a6d99a2de514e63580b5a"}, + {file = "backports_zstd-1.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae106fe16e36efc60ab098d02478d30aa0e31e1420eb4ecf0116459253bc6361"}, + {file = "backports_zstd-1.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7293fefe15f0e5852bdb4ad1e0e26f3cbd4d3e61c19f751ecc4ff34bc1eb237d"}, + {file = "backports_zstd-1.6.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ece8e7288db5b827ef8c64b2f78519f1a173a8991a625978fce02eccd7654fe9"}, + {file = "backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28eef3881164f3c23ce58ed59e4684103bdd279583eb2d299858c9e9b72fde9a"}, + {file = "backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:481a1e9bd8f419fdc625307aa20234687f99368c75df511ef589693c5fea4c6f"}, + {file = "backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3b6713371f8987a1178df93cb36f29eef191f224021e2d656b2f11ce60d26816"}, + {file = "backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b0ddbcd2866b8ff1a2836e4b0e4d44788f5b992d83fac75a38cda8f9a2bee079"}, + {file = "backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2914abea516704bdafb2090acd3f15b5f9debecfabd15b8dd8285b2ad3b92209"}, + {file = "backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd085eafa2aac6f883afd28210a3231f717f25409a1e44a39bb7b04c8c5b5646"}, + {file = "backports_zstd-1.6.0-cp311-cp311-win32.whl", hash = "sha256:b81b4cf3d6e0ad7ac92bef248f49fafc954262c5fb0f7e19d6aac497e5a856b2"}, + {file = "backports_zstd-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:10b61850c4112952e05aa6e6cce8c9a5936fbeadb321e154216705cc76a14afa"}, + {file = "backports_zstd-1.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:068ef3d8c18815a2e3a752f766313e19910e7c50939b956923748d9c04ebcb1b"}, + {file = "backports_zstd-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0466b14723f3b7697669c00ee66fe16e30e25636b286b0a923fa86fa3d8a753c"}, + {file = "backports_zstd-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d146926e997d2d3de8212bdcbf4985344a2622ca3bec458d8908000a84fd883"}, + {file = "backports_zstd-1.6.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:460fd6b3f338c659507ae36cfd6b58ac9942a2ff233c5cf574416dfec0451a84"}, + {file = "backports_zstd-1.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c2b1f4a640c51130caa92cef5bf72bd3c3dbbcfbf814c37403aa0601b1811b0"}, + {file = "backports_zstd-1.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:beb43e9885202c8d4f3762319ed4d5e98e197622afbff8439fbbdd81d08938b9"}, + {file = "backports_zstd-1.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fbb746522ebfc11155f1cd688e2c48ef3d74125e38b63eabdaab068a055c3e88"}, + {file = "backports_zstd-1.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a99710fbb225d459d66def4dc2bb2cd4a9a0bdc8b799fc0621cfdd863be9c93"}, + {file = "backports_zstd-1.6.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f69365ee2b836939137de024a302395a1cb8654fb6dc5ffef6381105259c8f87"}, + {file = "backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:66cf8038893c7708ec345ffb3ac63c775d10f430f323ac2f0334fdb6a397c57c"}, + {file = "backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e514c71ca72f3b56bd8fbda1a6a5b7d1100a2764b42a3c74a38841f25f9b00ab"}, + {file = "backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7741e44f7938ec94f9a52678c8d19b7bc548522ffdc39c9e4481af8db545fa9a"}, + {file = "backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97e8a9674652496c7612b528085dd5a296c052a2edc466ca1bfb7b0b27820413"}, + {file = "backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:23a793f2fed4dbf0517319759a2cded0b0dd8e8d3797fe30badd5693e320c175"}, + {file = "backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b951113113ed4b8d173418a4f155c14b739dace626b3fa3f82be1831958d39e4"}, + {file = "backports_zstd-1.6.0-cp312-cp312-win32.whl", hash = "sha256:6430b34a2ae6fcc604672f4f913102563473d9a015bdca1ce8c95041cc1f2677"}, + {file = "backports_zstd-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:08793876172551a930ce4d65c712cd516184d1a97070d4a1193e05bf0cf7040d"}, + {file = "backports_zstd-1.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:03b7c59c71f7a597e2bcb3f8368371e9a660a1bdf1c37afc1f1ad1496a013c19"}, + {file = "backports_zstd-1.6.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:2ace939e4d620e119423606f2d3d7115f8707733bf57f279ad9a9383f875986f"}, + {file = "backports_zstd-1.6.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:4c68a9ed2df0cca51d774c521e68a34d2e3d9ebfc687ef8096adfd4f345b551d"}, + {file = "backports_zstd-1.6.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:30576f49b82328ec8af16c11100efe52ca88526f71bbe100ef6b4e707dc13bf2"}, + {file = "backports_zstd-1.6.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b4bddfcfb6679215d6f4dc5f79a1f9301af339480d70527a14b57a1f2e6b6cbf"}, + {file = "backports_zstd-1.6.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:65048ed08c5124f05ff9f355ab9703014bb2dbe7f8d9948ce193685b1775f442"}, + {file = "backports_zstd-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5918fc6b31437208721276964323933cd86077b8d5b469c59c1b3fd2c8220a05"}, + {file = "backports_zstd-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b6c8b02ab0ccb2431bb7bc238be91d158b308915e7b07937388e540466fe7e7"}, + {file = "backports_zstd-1.6.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:711e6b98f8924e8b4a61ff97ab6321f33de024e1ed6a32f5123763aeda8459be"}, + {file = "backports_zstd-1.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ba9ac10fc393e5123a08802e0e895a107cb4a66b9973d2844dbd8a343111e59"}, + {file = "backports_zstd-1.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f723219335387d7546412d8141e0303590600949b4184a1391a0c6a3c756058"}, + {file = "backports_zstd-1.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64b94d7a836568926a3309ff510c7f8261b881b341fd4992cabf4f0998878f8a"}, + {file = "backports_zstd-1.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e39258a09b1c7ca70b5e94a5c5ccfe4700b4250b8077cfeab31d0f79565d4c9b"}, + {file = "backports_zstd-1.6.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:15b1aae0f64cd742df4bba1d989d0a09a6ec619202543fdba684640454541fd3"}, + {file = "backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25b5ddc789480072551af571a746e9500356b2aff0499861cf2ca07ea7431e68"}, + {file = "backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a13cfa3410a75e4cb87abdb669aaf79da861cb79299159054ff8f77b9671bc40"}, + {file = "backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2ddab55a5f54dec8acfad68ef70f1c704fd21919990ddc238afbd6f496e61c6a"}, + {file = "backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fa305a84087e10d7a85e8a8a3dcba8cdbda4868f2180173b264b7b488fd37c55"}, + {file = "backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:df27b57d214a3124fbe4e933ef5a903d4567f154260d9aece8c797a987f2a205"}, + {file = "backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:28fecd73459d74910ae1987ab84b7bef690d3dd860948430dd5555108b006daf"}, + {file = "backports_zstd-1.6.0-cp313-cp313-win32.whl", hash = "sha256:3e689af303df287142770abe3a48bbefd24dab4a09da5807d0e1fa8c75bab026"}, + {file = "backports_zstd-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:b067b1ef9c8e41fb0882c828aa37829938b5c0dab067eca72b23fc24c563b9da"}, + {file = "backports_zstd-1.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:a838296f5b84c920172fb579cac894d255c1fc25457c7234613ddcfa385e49b7"}, + {file = "backports_zstd-1.6.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6c73ae37dbf9207727ac095dedef864c05d836eaec962a47b3b64eaadaf1c6b6"}, + {file = "backports_zstd-1.6.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:839faf90a7eb525a401978dc925df8c44bd12526e8ba1529b9f8a7106e729637"}, + {file = "backports_zstd-1.6.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f8f5c1c7c69a4b00889e52d9304a918a5b49010f9645768eb5fd0ad404f790ba"}, + {file = "backports_zstd-1.6.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e80bceebc9b58e959bede9b26cafe15b5b9526f3533a6dd06330c5da73cb9329"}, + {file = "backports_zstd-1.6.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79284c1dd702f4f24ed1a36e51555c907dd237b6c0d829595978f4089a2aeea9"}, + {file = "backports_zstd-1.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1e20b3ecd0a711be82e964aca28554eabbc31ee69a20e5e7b8fd42268af46212"}, + {file = "backports_zstd-1.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:aeef8563b82ed4af328f98e5041c1b4800d86f68f857ffd1577d4d47dc9aa6cd"}, + {file = "backports_zstd-1.6.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb75e33131946fabd6319061df3b8b1d588fe0963183280e9b5f49f7772fc09"}, + {file = "backports_zstd-1.6.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:ef132cfb638e9a86bd5dc07fb4e1cb895bc55bce6bb5e759366e8b160d0747e2"}, + {file = "backports_zstd-1.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab70eace272d6f122b121c057e436709b50a28abf30d97aab28433c08f4a4095"}, + {file = "backports_zstd-1.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17efb3d11137de5166dd51eedab9c36ad633402acba386eee8d715213ea47e49"}, + {file = "backports_zstd-1.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:994167ff6551b9c1ce226e0aab16295b98c94507b5701aa60d2c32b7d50796b1"}, + {file = "backports_zstd-1.6.0.tar.gz", hash = "sha256:80a7859ffe70bf239d7a2ce15293bdeb5b4280ff7dc326ffab312b0e254dbb24"}, +] + [[package]] name = "certifi" version = "2026.4.22" @@ -11,6 +182,19 @@ files = [ {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, ] +[[package]] +name = "cfgraph" +version = "0.2.1" +description = "rdflib collections flattening graph" +optional = false +python-versions = "*" +files = [ + {file = "CFGraph-0.2.1.tar.gz", hash = "sha256:b57fe7044a10b8ff65aa3a8a8ddc7d4cd77bf511b42e57289cd52cbc29f8fe74"}, +] + +[package.dependencies] +rdflib = ">=0.4.2" + [[package]] name = "cfgv" version = "3.5.0" @@ -22,6 +206,153 @@ files = [ {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, ] +[[package]] +name = "chardet" +version = "7.4.3" +description = "Universal character encoding detector" +optional = false +python-versions = ">=3.10" +files = [ + {file = "chardet-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c79b13c9908ac7dfe0a74116ebc9a0f28b2319d23c32f3dfcdfbe1279c7eaf"}, + {file = "chardet-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bba8bea1b28d927b3e99e47deafe53658d34497c0a891d95ff1ba8ff6663f01c"}, + {file = "chardet-7.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23163921dccf3103ce59540b0443c106d2c0a0ff2e0503e05196f5e6fdea453f"}, + {file = "chardet-7.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfb54563fe5f130da17c44c6a4e2e8052ba628e5ab4eab7ef8190f736f0f8f72"}, + {file = "chardet-7.4.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3990fffcc6a6045f2234ab72752ad037e3b2d48c72037f244d42738db397eb75"}, + {file = "chardet-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c7116b0452994734ccff35e154b44240090eb0f4f74b9106292668133557c175"}, + {file = "chardet-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:25a862cddc6a9ac07023e808aedd297115345fbaabc2690479481ddc0f980e09"}, + {file = "chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97"}, + {file = "chardet-7.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc50f28bad067393cce0af9091052c3b8df7a23115afd8ba7b2e0947f0cef1f8"}, + {file = "chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9"}, + {file = "chardet-7.4.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c45e116dd51b66226a53ade3f9f635e870de5399b90e00ce45dcc311093bf4"}, + {file = "chardet-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578"}, + {file = "chardet-7.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:75d3c65cc16bddf40b8da1fd25ba84fca5f8070f2b14e86083653c1c85aee971"}, + {file = "chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a"}, + {file = "chardet-7.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:626f00299ad62dfe937058a09572beed442ccc7b58f87aa667949b20fd3db235"}, + {file = "chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a4904dd5f071b7a7d7f50b4a67a86db3c902d243bf31708f1d5cde2f68239cb"}, + {file = "chardet-7.4.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d2879598bc220689e8ce509fe9c3f37ad2fca53a36be9c9bd91abdd91dd364f"}, + {file = "chardet-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:4b2799bd58e7245cfa8d4ab2e8ad1d76a5c3a5b1f32318eb6acca4c69a3e7101"}, + {file = "chardet-7.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e4486df251b8962e86ea9f139ca235aa6e0542a00f7844c9a04160afb99aa9"}, + {file = "chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a"}, + {file = "chardet-7.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:365135eaf37ba65a828f8e668eb0a8c38c479dcbec724dc25f4dfd781049c357"}, + {file = "chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d"}, + {file = "chardet-7.4.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9acd9988a93e09390f3cd231201ea7166c415eb8da1b735928990ffc05cb9fbb"}, + {file = "chardet-7.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131"}, + {file = "chardet-7.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d892d3dcd652fdef53e3d6327d39b17c0df40a899dfc919abaeb64c974497531"}, + {file = "chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:acc46d1b8b7d5783216afe15db56d1c179b9a40e5a1558bc13164c4fd20674c4"}, + {file = "chardet-7.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ac3bf11c645734a1701a3804e43eabd98851838192267d08c353a834ab79fea"}, + {file = "chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e3bd9f936e04bae89c254262af08d9e5b98f805175ba1e29d454e6cba3107b7"}, + {file = "chardet-7.4.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:27cc23da03630cdecc9aa81a895aa86629c211f995cd57651f0fbc280717bf93"}, + {file = "chardet-7.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:b95c934b9ad59e2ba8abb9be49df70d3ad1b0d95d864b9fdb7588d4fa8bd921c"}, + {file = "chardet-7.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c77867f0c1cb8bd819502249fcdc500364aedb07881e11b743726fa2148e7b6e"}, + {file = "chardet-7.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cf1efeaf65a6ef2f5b9cc3a1df6f08ba2831b369ccaa4c7018eaf90aa757bb11"}, + {file = "chardet-7.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f3504c139a2ad544077dd2d9e412cd08b01786843d76997cd43bb6de311723c"}, + {file = "chardet-7.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457f619882ba66327d4d8d14c6c342269bdb1e4e1c38e8117df941d14d351b04"}, + {file = "chardet-7.4.3-py3-none-any.whl", hash = "sha256:1173b74051570cf08099d7429d92e4882d375ad4217f92a6e5240ccfb26f231e"}, + {file = "chardet-7.4.3.tar.gz", hash = "sha256:cc1d4eb92a4ec1c2df3b490836ffa46922e599d34ce0bb75cf41fd2bf6303d56"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +files = [ + {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win32.whl", hash = "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_arm64.whl", hash = "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe"}, + {file = "charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5"}, + {file = "charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b"}, +] + [[package]] name = "click" version = "8.3.3" @@ -168,6 +499,47 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "curies" +version = "0.14.1" +description = "Idiomatic conversion between URIs and compact URIs (CURIEs)" +optional = false +python-versions = ">=3.10" +files = [ + {file = "curies-0.14.1-py3-none-any.whl", hash = "sha256:04de52080536881108b52ca864e8d4096140a7c3f5f556d8c2836c8ff99adc8f"}, + {file = "curies-0.14.1.tar.gz", hash = "sha256:543942892d1da01ba59e63720d223d504d97908d5c2c83458f33af54b87c92b3"}, +] + +[package.dependencies] +pydantic = ">=2.0" +pystow = ">=0.8.0" +typing-extensions = "*" + +[package.extras] +fastapi = ["defusedxml", "fastapi", "httpx", "python-multipart", "uvicorn"] +flask = ["defusedxml", "flask"] +pandas = ["pandas"] +rdflib = ["rdflib"] +sqlalchemy = ["sqlalchemy"] +sqlmodel = ["sqlmodel"] + +[[package]] +name = "deprecated" +version = "1.3.1" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ + {file = "deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f"}, + {file = "deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223"}, +] + +[package.dependencies] +wrapt = ">=1.10,<3" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] + [[package]] name = "distlib" version = "0.4.0" @@ -179,6 +551,28 @@ files = [ {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, + {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -207,6 +601,136 @@ files = [ {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, ] +[[package]] +name = "fqdn" +version = "1.5.1" +description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +optional = false +python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "graphviz" +version = "0.21" +description = "Simple Python interface for Graphviz" +optional = false +python-versions = ">=3.9" +files = [ + {file = "graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42"}, + {file = "graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78"}, +] + +[package.extras] +dev = ["Flake8-pyproject", "build", "flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5,<7)", "sphinx-autodoc-typehints", "sphinx-rtd-theme (>=0.2.5)"] +test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] + +[[package]] +name = "greenlet" +version = "3.5.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.10" +files = [ + {file = "greenlet-3.5.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c180d22d325fb613956b443c3c6f4406eb70e6defc70d3974da2a7b59e06f48c"}, + {file = "greenlet-3.5.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483d08c11181c83a6ce1a7a61df0f624a208ec40817a3bb2302714592eee4f04"}, + {file = "greenlet-3.5.3-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1dae6e0091eae084317e411f047f0b7cb241c6db570f7c45fd6b900a274914ce"}, + {file = "greenlet-3.5.3-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0f6ff50ff8dbd51fae9b37f4101648b04ea0df19b3f50ab2beb5061e7716a5c8"}, + {file = "greenlet-3.5.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcd2d72ccd70a1ec68ba6ef93e7fbb4420ef9997dabc7010d893bd4015e0bec"}, + {file = "greenlet-3.5.3-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:37bf9c538f5ae6e63d643f88dec37c0c83bdf0e2ebc62961dedcf458822f7b71"}, + {file = "greenlet-3.5.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73f152c895e09907e0dbe24f6c2db37beb085cd63db91c3825a0fcd0064124a8"}, + {file = "greenlet-3.5.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8bdb43e1a1d1873721acab2be99c5befd4d2044ddfd52e4d610801019880a702"}, + {file = "greenlet-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:0909f9355a9f24845d3299f3112e266a06afb68302041989fd26bd68894933db"}, + {file = "greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4"}, + {file = "greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc"}, + {file = "greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6"}, + {file = "greenlet-3.5.3-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e18619ba655ac05d78d80fc83cac4ba892bd6927b99e3b8237aee861aaacc8bb"}, + {file = "greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7"}, + {file = "greenlet-3.5.3-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:d27c0c653a60d9535f690226474a5cc1036a8b0d7b57504d1c4f89c44a07a80c"}, + {file = "greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8"}, + {file = "greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8"}, + {file = "greenlet-3.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7"}, + {file = "greenlet-3.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44"}, + {file = "greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2"}, + {file = "greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b"}, + {file = "greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab"}, + {file = "greenlet-3.5.3-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5795cd1101371140551c645f2d408b8d3c01a5a29cf8a9bce6e759c983682d23"}, + {file = "greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861"}, + {file = "greenlet-3.5.3-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:af4923b3096e26a36d7e9cf24ab88083a20f97d191e3b97f253731ce9b41b28c"}, + {file = "greenlet-3.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149"}, + {file = "greenlet-3.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea"}, + {file = "greenlet-3.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:cde8adafa2365676f74a979744629589999093bc86e2484214f58e61df08902c"}, + {file = "greenlet-3.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:c4e7b79d83805475f0102008843f6eb45fd3bb0b2e88c774adab5fbaab27117d"}, + {file = "greenlet-3.5.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c8d87c2134d871df96ecdea9cec7cbaab286dadab0f56476e57aaf9e8ac11550"}, + {file = "greenlet-3.5.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2d185dd1621757e70c3861cceffd5317ab4e7ed7eb09c82994828468527ade5"}, + {file = "greenlet-3.5.3-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1c514a468149bf8fbbab874188a3535cd8a48a3e353eb53a3d424296f8dbacd3"}, + {file = "greenlet-3.5.3-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9ad04dd75458c6300b047c61b8639092433d205a25a14e310d6582a480efcca1"}, + {file = "greenlet-3.5.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:915f887cf2682b66419b879423a2e072634aa7b7dce6f3ada4957cfced3f1e9a"}, + {file = "greenlet-3.5.3-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:afaabdd554cd7ae9bbb3ca070b0d7fdfd207dbf1d16865f7233837709d354bda"}, + {file = "greenlet-3.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:766cfd421c13e450feb340cd472a3ed9957d438727b7b4593ad7c76c5d2b0deb"}, + {file = "greenlet-3.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2ecda9ec22edf38fa389369eaed8c3d37c05f3c54e69f69438dbb2cc1de1458b"}, + {file = "greenlet-3.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:c82304750f057167ff60d188df1d0cc1764ce9567eadf03e6a7443bcedd0b30b"}, + {file = "greenlet-3.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:dc133a1569ee667b2a6ef56ce551084aeefd87a5acbc4736d336d1e2edc6cfc4"}, + {file = "greenlet-3.5.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:fd2e02fa07485778536a036222d616ab957b1d533f36b3ed98ce725d9c9d3117"}, + {file = "greenlet-3.5.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df0a0628d1597eb0897b62f55d1343f772405fd25f3b2a796c76874b0c2e22e8"}, + {file = "greenlet-3.5.3-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ebd933a6adabc298bab47731a130fe6bfb888bd934eee37810f151159544540d"}, + {file = "greenlet-3.5.3-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8d19fe6c39ebff9259f07bcc685d3290f8fa4ea2278e51dd0008e4d6b0f2d814"}, + {file = "greenlet-3.5.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b9d501b40e80b70e32323c799dd9b420a5577a9601469d362ae1ffb690f3a7c"}, + {file = "greenlet-3.5.3-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:962c5df2db8cb446da51edf1ca5296c389d93b99c9d8aa2ee4c7d0d8f1218260"}, + {file = "greenlet-3.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a1fad1d11e7d6aab184107baa8e4ece11ccba3ec9599cd7efa5ff4d70d43256a"}, + {file = "greenlet-3.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fad5aec764399f1b5cc347ad250a59660f20c8f8888ea6bae1f93b769cce1154"}, + {file = "greenlet-3.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:7669aa24cf2a1041d6f7899575b494a3ab4cf68bfcc8609b1dc0be7272db835e"}, + {file = "greenlet-3.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:5b4807c4082c9d1b6d9eed56fcd041863e37f2228106eef24c30ca096e238605"}, + {file = "greenlet-3.5.3-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:271a8ea7c1024e8a0d7dd2be66dd66dda8a07193f41a17b9e924f7600f5b62be"}, + {file = "greenlet-3.5.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19131729ae0ddc3c2e1ef85e650169b5e37ee32e400f215f78b94d7b0d567310"}, + {file = "greenlet-3.5.3-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1540dd8e5fc2a5aec40fbb98ef8e149fa47c89a4b4a1cf2575a14d3d1869d7a8"}, + {file = "greenlet-3.5.3-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b897d97759425953f69a9c0fac67f8fe333ec0ce7377ef186fb2b0c3ad5e354d"}, + {file = "greenlet-3.5.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e81fa194a1d20967877bdf9c7794db2bc99063e5be36aee710c08f04c5bb087f"}, + {file = "greenlet-3.5.3-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:3236754d423955ea08e9bb5f6c04a7895f9e22c290b66aa7653fcb922d839eb0"}, + {file = "greenlet-3.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55cf4d777485d43110e47133cbba6d74a8885a87ec1227ef0267f9ee80c5aa21"}, + {file = "greenlet-3.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:12a248ba75f6a9a236375f52296c498c89ff1d8badf32deb9eca7abd5853f7da"}, + {file = "greenlet-3.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:efc6bd60ea02e085862c74a3ef64b147ffc6f1a5ea7d9f26e7a939943f68c1e3"}, + {file = "greenlet-3.5.3-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:ea03f2f04367845d6b58eeed276e1e56e51f0b97d8ad5a88a7d20a91dc9056cc"}, + {file = "greenlet-3.5.3-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78dbef602fda6d97d957eb7937f70c9ce9e9527330347f8f6b6f9e554a9e7a47"}, + {file = "greenlet-3.5.3-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f73857adb8fee13fa56c172bd11262f888c0c648f9fea113e777bb2c7904a81"}, + {file = "greenlet-3.5.3-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cefa9cef4b371f9844c6053db71f1138bc6807bab1578b0dae5149c1f1141357"}, + {file = "greenlet-3.5.3-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:232fec92e823addaf02d9472cf7381e24a1d046a6ced1103c5caa4c21b9dfc1d"}, + {file = "greenlet-3.5.3-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:6219b6d04dbf6ba6084d77dc609e8473060dc55f759cbf626d512122781fa128"}, + {file = "greenlet-3.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2421c3564da9429d5586d46ca31ebb26516b5498a802cf65c041a8e8a8980d34"}, + {file = "greenlet-3.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e0f0d160f0b2e558e6c75f7930967183255dc9735e5f5b8cae58ee09c9576d8b"}, + {file = "greenlet-3.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:dd99329bbc15ca78dcc583dba05d0b1b0bae01ab6c2174989f5aaee3e41ac930"}, + {file = "greenlet-3.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:499fef2acede88c1864a57bb586b4bf533c81e1b82df7ab93451cdb47dfec227"}, + {file = "greenlet-3.5.3-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:176bc16a721fa5fc294d70b87b4dfa5fbdd251b3da5d5372735ecef9bd7d6d0c"}, + {file = "greenlet-3.5.3-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:629b614d2b786e89c50440e246f33eea78f58a962d0bdbbcc809e6d13605903f"}, + {file = "greenlet-3.5.3-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b2e857ae16f5f72142edf75f9f176fe7526ba19a2841df1420516f83831c9f2"}, + {file = "greenlet-3.5.3-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d192579ed281051396dddd7f7754dac6259e6b1fb26378c87b66622f8e3f91"}, + {file = "greenlet-3.5.3-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e515757e2e36bcbf1fad09a46e1557e8b1ae1797d4b44d09da7deed88ad28608"}, + {file = "greenlet-3.5.3-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:4399eb8d041f20b68d943918bc55502a93d6fdc0a37c14da7881c04139acee9d"}, + {file = "greenlet-3.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b363d46ed1ea431825fdb01471bb024fc08399bad1572a616e853c7684415adb"}, + {file = "greenlet-3.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:e44da2f5bbdaabaf7d80b73dbb430c7035771e9f244e3c8b769715c9d8fa0a16"}, + {file = "greenlet-3.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:8ff8bed3e3baa20a3ea261ce00526f1898ad4801d4886fd2220580ee0ad8fadf"}, + {file = "greenlet-3.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:b7068bd09f761f3f5b4d214c2bed063186b2a86148c740b3873e3f56d79bac31"}, + {file = "greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil", "setuptools"] + +[[package]] +name = "hbreader" +version = "0.9.1" +description = "Honey Badger reader - a generic file/url/string open and read tool" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hbreader-0.9.1-py3-none-any.whl", hash = "sha256:9a6e76c9d1afc1b977374a5dc430a1ebb0ea0488205546d4678d6e31cc5f6801"}, + {file = "hbreader-0.9.1.tar.gz", hash = "sha256:d2c132f8ba6276d794c66224c3297cec25c8079d0a4cf019c061611e0a3b94fa"}, +] + [[package]] name = "identify" version = "2.6.19" @@ -221,6 +745,31 @@ files = [ [package.extras] license = ["ukkonen"] +[[package]] +name = "idna" +version = "3.18" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, + {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, +] + +[package.extras] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imagesize" +version = "1.5.0" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ + {file = "imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899"}, + {file = "imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f"}, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -247,6 +796,31 @@ files = [ pytz = "*" six = "*" +[[package]] +name = "isodate" +version = "0.7.2" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, + {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +description = "Operations with ISO 8601 durations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[package.dependencies] +arrow = ">=0.15.0" + [[package]] name = "isort" version = "5.13.2" @@ -261,6 +835,117 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "json-flattener" +version = "0.1.9" +description = "Python library for denormalizing nested dicts or json objects to tables and back" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "json_flattener-0.1.9-py3-none-any.whl", hash = "sha256:6b027746f08bf37a75270f30c6690c7149d5f704d8af1740c346a3a1236bc941"}, + {file = "json_flattener-0.1.9.tar.gz", hash = "sha256:84cf8523045ffb124301a602602201665fcb003a171ece87e6f46ed02f7f0c15"}, +] + +[package.dependencies] +click = "*" +pyyaml = "*" + +[[package]] +name = "jsonasobj" +version = "1.3.1" +description = "JSON as python objects" +optional = false +python-versions = "*" +files = [ + {file = "jsonasobj-1.3.1-py3-none-any.whl", hash = "sha256:b9e329dc1ceaae7cf5d5b214684a0b100e0dad0be6d5bbabac281ec35ddeca65"}, + {file = "jsonasobj-1.3.1.tar.gz", hash = "sha256:d52e0544a54a08f6ea3f77fa3387271e3648655e0eace2f21e825c26370e44a2"}, +] + +[[package]] +name = "jsonasobj2" +version = "1.0.4" +description = "JSON as python objects - version 2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "jsonasobj2-1.0.4-py3-none-any.whl", hash = "sha256:12e86f86324d54fcf60632db94ea74488d5314e3da554c994fe1e2c6f29acb79"}, + {file = "jsonasobj2-1.0.4.tar.gz", hash = "sha256:f50b1668ef478004aa487b2d2d094c304e5cb6b79337809f4a1f2975cc7fbb4e"}, +] + +[package.dependencies] +hbreader = "*" + +[[package]] +name = "jsonpointer" +version = "3.1.1" +description = "Identify specific nodes in a JSON document (RFC 6901) " +optional = false +python-versions = ">=3.10" +files = [ + {file = "jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca"}, + {file = "jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900"}, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, + {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +fqdn = {version = "*", optional = true, markers = "extra == \"format\""} +idna = {version = "*", optional = true, markers = "extra == \"format\""} +isoduration = {version = "*", optional = true, markers = "extra == \"format\""} +jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format\""} +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format\""} +rfc3987 = {version = "*", optional = true, markers = "extra == \"format\""} +rpds-py = ">=0.25.0" +uri-template = {version = "*", optional = true, markers = "extra == \"format\""} +webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +files = [ + {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, + {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + [[package]] name = "librt" version = "0.9.0" @@ -360,6 +1045,172 @@ files = [ {file = "librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d"}, ] +[[package]] +name = "linkml" +version = "1.11.1" +description = "Linked Open Data Modeling Language" +optional = false +python-versions = ">=3.10" +files = [ + {file = "linkml-1.11.1-py3-none-any.whl", hash = "sha256:d1bbb97a8b1ea4a99b145007875733a5e5e89b3acfe3e9d1e369fa4a582990ed"}, + {file = "linkml-1.11.1.tar.gz", hash = "sha256:2f6774e13628270cadaeecda3313db0437ecc15cd44ee35c6c2655dbe31c8524"}, +] + +[package.dependencies] +antlr4-python3-runtime = ">=4.9.0,<4.10" +click = ">=8.2" +graphviz = ">=0.10.1" +hbreader = "*" +isodate = ">=0.6.0" +jinja2 = ">=3.1.0" +jsonasobj2 = ">=1.0.3,<2.0.0" +jsonschema = {version = ">=4.0.0", extras = ["format"]} +linkml-runtime = ">=1.10.0,<2.0.0" +openpyxl = "*" +parse = "*" +prefixcommons = ">=0.1.7" +prefixmaps = ">=0.2.2" +pydantic = ">=2.0.0,<3.0.0" +pyjsg = ">=0.11.6" +pyshex = ">=0.7.20" +pyshexc = ">=0.8.3" +python-dateutil = "*" +pyyaml = "*" +rdflib = ">=6.0.0" +requests = ">=2.22" +sphinx-click = ">=6.0.0" +sqlalchemy = ">=1.4.31" +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.12\""} +watchdog = ">=0.9.0" + +[[package]] +name = "linkml-runtime" +version = "1.11.1" +description = "Runtime environment for LinkML, the Linked open data modeling language" +optional = false +python-versions = ">=3.10" +files = [ + {file = "linkml_runtime-1.11.1-py3-none-any.whl", hash = "sha256:b22c77d8fd920d0f4f43a6ece31393dc0b28bb47790f3e1c114210318c36b3da"}, + {file = "linkml_runtime-1.11.1.tar.gz", hash = "sha256:e71300b596c4f35aeccd9dca096806678402213dbdb2c5e8e68f507e21320754"}, +] + +[package.dependencies] +click = ">=8.2" +curies = ">=0.5.4" +deprecated = "*" +hbreader = "*" +isodate = {version = ">=0.7.2,<1.0.0", markers = "python_version < \"3.11\""} +json-flattener = ">=0.1.9" +jsonasobj2 = ">=1.0.4,<2.dev0" +jsonschema = ">=3.2.0" +prefixcommons = ">=0.1.12" +prefixmaps = ">=0.1.4" +pydantic = ">=1.10.2,<3.0.0" +pyyaml = "*" +rdflib = ">=6.0.0" +requests = "*" + +[package.extras] +dev = ["coverage", "requests-cache"] + +[[package]] +name = "markupsafe" +version = "3.0.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + [[package]] name = "monotonic" version = "1.6" @@ -528,6 +1379,20 @@ files = [ {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] +[[package]] +name = "openpyxl" +version = "3.1.5" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, + {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, +] + +[package.dependencies] +et-xmlfile = "*" + [[package]] name = "packaging" version = "26.2" @@ -667,6 +1532,17 @@ files = [ [package.dependencies] pillow = "*" +[[package]] +name = "parse" +version = "1.22.1" +description = "parse() is the opposite of format()" +optional = false +python-versions = "*" +files = [ + {file = "parse-1.22.1-py2.py3-none-any.whl", hash = "sha256:20f0925a46f06602485ac90d751764d0697fd8455aaa97489ba8953a4b66de32"}, + {file = "parse-1.22.1.tar.gz", hash = "sha256:d3a4740ec3da338e2b258b2d69741b731eadfddca59e24a14bc4ee5fce38c911"}, +] + [[package]] name = "pathspec" version = "1.1.1" @@ -836,19 +1712,51 @@ pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] -name = "py2neo" -version = "2021.2.4" -description = "Python client library and toolkit for Neo4j" +name = "prefixcommons" +version = "0.1.12" +description = "A python API for working with ID prefixes" optional = false -python-versions = "*" +python-versions = ">=3.7,<4.0" files = [ - {file = "py2neo-2021.2.4-py2.py3-none-any.whl", hash = "sha256:2ddbe818354a6fa16d47dfd0fe5cb0287fa42ff109e87aa7b3e43636060d85a1"}, - {file = "py2neo-2021.2.4.tar.gz", hash = "sha256:4b2737fcd9fd8d82b57e856de4eda005281c9cf0741c989e5252678f0503f77e"}, + {file = "prefixcommons-0.1.12-py3-none-any.whl", hash = "sha256:16dbc0a1f775e003c724f19a694fcfa3174608f5c8b0e893d494cf8098ac7f8b"}, + {file = "prefixcommons-0.1.12.tar.gz", hash = "sha256:22c4e2d37b63487b3ab48f0495b70f14564cb346a15220f23919eb0c1851f69f"}, ] [package.dependencies] -certifi = "*" -interchange = ">=2021.0.4,<2021.1.0" +click = ">=8.1.3,<9.0.0" +pytest-logging = ">=2015.11.4,<2016.0.0" +PyYAML = ">=6.0,<7.0" +requests = ">=2.28.1,<3.0.0" + +[[package]] +name = "prefixmaps" +version = "0.2.6" +description = "A python library for retrieving semantic prefix maps" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "prefixmaps-0.2.6-py3-none-any.whl", hash = "sha256:f6cef28a7320fc6337cf411be212948ce570333a0ce958940ef684c7fb192a62"}, + {file = "prefixmaps-0.2.6.tar.gz", hash = "sha256:7421e1244eea610217fa1ba96c9aebd64e8162a930dc0626207cd8bf62ecf4b9"}, +] + +[package.dependencies] +curies = ">=0.5.3" +pyyaml = ">=5.3.1" + +[[package]] +name = "py2neo" +version = "2021.2.4" +description = "Python client library and toolkit for Neo4j" +optional = false +python-versions = "*" +files = [ + {file = "py2neo-2021.2.4-py2.py3-none-any.whl", hash = "sha256:2ddbe818354a6fa16d47dfd0fe5cb0287fa42ff109e87aa7b3e43636060d85a1"}, + {file = "py2neo-2021.2.4.tar.gz", hash = "sha256:4b2737fcd9fd8d82b57e856de4eda005281c9cf0741c989e5252678f0503f77e"}, +] + +[package.dependencies] +certifi = "*" +interchange = ">=2021.0.4,<2021.1.0" monotonic = "*" packaging = "*" pansi = ">=2020.7.3" @@ -907,6 +1815,159 @@ numpy = ">=1.16.6" [package.extras] test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] +[[package]] +name = "pydantic" +version = "2.13.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.46.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + [[package]] name = "pygments" version = "2.20.0" @@ -921,6 +1982,105 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyjsg" +version = "0.12.4" +description = "Python JSON Schema Grammar interpreter" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pyjsg-0.12.4-py3-none-any.whl", hash = "sha256:a57ae58bfd7192b32654a0024bc6462fb459d54e837f0b2b5cff0726aad2e557"}, + {file = "pyjsg-0.12.4.tar.gz", hash = "sha256:bb1c0ff1f50846d2b5185b182e28b0b6978eae51a2078ce3eb1e0f28dea7b9ab"}, +] + +[package.dependencies] +antlr4-python3-runtime = ">=4.9.3,<4.10.0" +jsonasobj = ">=1.2.1" +requests = "*" + +[[package]] +name = "pyparsing" +version = "3.3.2" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyshex" +version = "0.9.0" +description = "Python ShEx interpreter" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pyshex-0.9.0-py3-none-any.whl", hash = "sha256:d81344deed686b7c169f23156221ae281225e2ba02b14fe9810335afdefffa9d"}, + {file = "pyshex-0.9.0.tar.gz", hash = "sha256:87288b5e5613f734f55f0085334558218ff618fb1061aabdcee19841092b3eca"}, +] + +[package.dependencies] +cfgraph = ">=0.2.1" +chardet = "*" +pyshexc = ">=0.10.3" +rdflib-shim = "*" +requests = ">=2.22.0" +shexjsg = ">=0.9.0" +sparqlslurper = ">=0.5.1" +sparqlwrapper = ">=1.8.5" +urllib3 = "*" + +[[package]] +name = "pyshexc" +version = "0.10.3.post1" +description = "PyShExC - Python ShEx compiler" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pyshexc-0.10.3.post1-py3-none-any.whl", hash = "sha256:5d247f2822ef9864152545935d93a07dce66640608ea9414c96f69da7fe7a168"}, + {file = "pyshexc-0.10.3.post1.tar.gz", hash = "sha256:80d9d067c80af9a796e3c1c47d2207edf2e9a9fc39d3ca0ce5dd2019334ea915"}, +] + +[package.dependencies] +antlr4-python3-runtime = ">=4.9.3,<4.10.0" +chardet = ">=7.4.1" +jsonasobj = ">=1.2.1" +pyjsg = ">=0.11.10" +rdflib-shim = ">=1.0.3" +shexjsg = ">=0.8.1" + +[[package]] +name = "pystow" +version = "0.8.21" +description = "Easily pick a place to store data for your Python code" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pystow-0.8.21-py3-none-any.whl", hash = "sha256:7b14f77f0395b93a3a94d85526972c98d526d86a8efa5036363b9da3e2d34003"}, + {file = "pystow-0.8.21.tar.gz", hash = "sha256:460c299093d3e6f45433141ba0c5bc5d99c7fe98b042a6f578040d5103db7aab"}, +] + +[package.dependencies] +backports-zstd = {version = "*", markers = "python_full_version < \"3.14\""} +tqdm = "*" +typing-extensions = "*" + +[package.extras] +aws = ["boto3"] +bs4 = ["bs4", "requests"] +cli = ["click"] +pandas = ["pandas"] +pydantic = ["pydantic"] +ratelimit = ["ratelimit", "requests"] +rdf = ["rdflib"] +requests = ["requests"] +xml = ["lxml"] +yaml = ["pyyaml"] + [[package]] name = "pytest" version = "9.0.3" @@ -963,6 +2123,19 @@ pytest = ">=7" [package.extras] testing = ["process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-logging" +version = "2015.11.4" +description = "Configures logging and allows tweaking the log level with a py.test flag" +optional = false +python-versions = "*" +files = [ + {file = "pytest-logging-2015.11.4.tar.gz", hash = "sha256:cec5c85ecf18aab7b2ead5498a31b9f758680ef5a902b9054ab3f2bdbb77c896"}, +] + +[package.dependencies] +pytest = ">=2.8.1" + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1103,6 +2276,245 @@ files = [ {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] +[[package]] +name = "rdflib" +version = "7.6.0" +description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "rdflib-7.6.0-py3-none-any.whl", hash = "sha256:30c0a3ebf4c0e09215f066be7246794b6492e054e782d7ac2a34c9f70a15e0dd"}, + {file = "rdflib-7.6.0.tar.gz", hash = "sha256:6c831288d5e4a5a7ece85d0ccde9877d512a3d0f02d7c06455d00d6d0ea379df"}, +] + +[package.dependencies] +isodate = {version = ">=0.7.2,<1.0.0", markers = "python_version < \"3.11\""} +pyparsing = ">=2.1.0,<4" + +[package.extras] +berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] +graphdb = ["httpx (>=0.28.1,<0.29.0)"] +html = ["html5rdf (>=1.2,<2)"] +lxml = ["lxml (>=4.3,<6.0)"] +networkx = ["networkx (>=2,<4)"] +orjson = ["orjson (>=3.9.14,<4)"] +rdf4j = ["httpx (>=0.28.1,<0.29.0)"] + +[[package]] +name = "rdflib-jsonld" +version = "0.6.1" +description = "rdflib extension adding JSON-LD parser and serializer" +optional = false +python-versions = "*" +files = [ + {file = "rdflib-jsonld-0.6.1.tar.gz", hash = "sha256:eda5a42a2e09f80d4da78e32b5c684bccdf275368f1541e6b7bcddfb1382a0e0"}, + {file = "rdflib_jsonld-0.6.1-py2.py3-none-any.whl", hash = "sha256:bcf84317e947a661bae0a3f2aee1eced697075fc4ac4db6065a3340ea0f10fc2"}, +] + +[package.dependencies] +rdflib = ">=5.0.0" + +[[package]] +name = "rdflib-shim" +version = "1.0.3" +description = "Shim for rdflib 5 and 6 incompatibilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rdflib_shim-1.0.3-py3-none-any.whl", hash = "sha256:7a853e7750ef1e9bf4e35dea27d54e02d4ed087de5a9e0c329c4a6d82d647081"}, + {file = "rdflib_shim-1.0.3.tar.gz", hash = "sha256:d955d11e2986aab42b6830ca56ac6bc9c893abd1d049a161c6de2f1b99d4fc0d"}, +] + +[package.dependencies] +rdflib = ">=5.0.0" +rdflib-jsonld = "0.6.1" + +[[package]] +name = "referencing" +version = "0.37.0" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, + {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + +[[package]] +name = "requests" +version = "2.34.2" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.10" +files = [ + {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, + {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, +] + +[package.dependencies] +certifi = ">=2023.5.7" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.26,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +description = "A pure python RFC3339 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "rfc3987" +version = "1.3.8" +description = "Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)" +optional = false +python-versions = "*" +files = [ + {file = "rfc3987-1.3.8-py2.py3-none-any.whl", hash = "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53"}, + {file = "rfc3987-1.3.8.tar.gz", hash = "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733"}, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.10" +files = [ + {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, + {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, + {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, + {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, + {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, + {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, + {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, + {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, + {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, + {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, + {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, + {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, + {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, + {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, + {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, + {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, + {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, + {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, + {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, + {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, + {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, + {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, + {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, + {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, + {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, + {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, + {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, + {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, + {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, + {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, + {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, + {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, + {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, + {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, + {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, + {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, + {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, + {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, + {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, + {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, + {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, + {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, + {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, + {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, + {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, + {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, + {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, + {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, + {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, + {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, + {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, + {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, + {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, + {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, +] + [[package]] name = "ruff" version = "0.3.7" @@ -1192,6 +2604,20 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "shexjsg" +version = "0.9.0" +description = "ShExJSG - Astract Syntax Tree Definition for the ShEx 2.0 language" +optional = false +python-versions = ">=3.10" +files = [ + {file = "shexjsg-0.9.0-py3-none-any.whl", hash = "sha256:abf18db2d9895bc46740f68ae699b2ccfe08c783f6e0c038e6077293ad01c0a5"}, + {file = "shexjsg-0.9.0.tar.gz", hash = "sha256:750016fabdb5487b27e2e714145f3602cd3ac4eb0dd9b7d7751d0cde62c0d1d8"}, +] + +[package.dependencies] +pyjsg = ">=0.12.3" + [[package]] name = "six" version = "1.17.0" @@ -1203,6 +2629,298 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "snowballstemmer" +version = "3.1.1" +description = "This package provides 36 stemmers for 34 languages generated from Snowball algorithms." +optional = false +python-versions = ">=3.3" +files = [ + {file = "snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752"}, + {file = "snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260"}, +] + +[[package]] +name = "sparqlslurper" +version = "0.5.1" +description = "SPARQL Slurper for rdflib" +optional = false +python-versions = ">=3.7.4" +files = [ + {file = "sparqlslurper-0.5.1-py3-none-any.whl", hash = "sha256:ae49b2d8ce3dd38df7a40465b228ad5d33fb7e11b3f248d195f9cadfc9cfff87"}, + {file = "sparqlslurper-0.5.1.tar.gz", hash = "sha256:9282ebb064fc6152a58269d194cb1e7b275b0f095425a578d75b96dcc851f546"}, +] + +[package.dependencies] +rdflib = ">=5.0.0" +rdflib-shim = "*" +sparqlwrapper = ">=1.8.2" + +[[package]] +name = "sparqlwrapper" +version = "2.0.0" +description = "SPARQL Endpoint interface to Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SPARQLWrapper-2.0.0-py3-none-any.whl", hash = "sha256:c99a7204fff676ee28e6acef327dc1ff8451c6f7217dcd8d49e8872f324a8a20"}, + {file = "SPARQLWrapper-2.0.0.tar.gz", hash = "sha256:3fed3ebcc77617a4a74d2644b86fd88e0f32e7f7003ac7b2b334c026201731f1"}, +] + +[package.dependencies] +rdflib = ">=6.1.1" + +[package.extras] +dev = ["mypy (>=0.931)", "pandas (>=1.3.5)", "pandas-stubs (>=1.2.0.48)", "setuptools (>=3.7.1)"] +docs = ["sphinx (<5)", "sphinx-rtd-theme"] +keepalive = ["keepalive (>=0.5)"] +pandas = ["pandas (>=1.3.5)"] + +[[package]] +name = "sphinx" +version = "8.1.3" +description = "Python documentation generator" +optional = false +python-versions = ">=3.10" +files = [ + {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, + {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, +] + +[package.dependencies] +alabaster = ">=0.7.14" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = ">=1.0.7" +sphinxcontrib-devhelp = ">=1.0.6" +sphinxcontrib-htmlhelp = ">=2.0.6" +sphinxcontrib-jsmath = ">=1.0.1" +sphinxcontrib-qthelp = ">=1.0.6" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] + +[[package]] +name = "sphinx-click" +version = "6.2.0" +description = "Sphinx extension that automatically documents click applications" +optional = false +python-versions = ">=3.10" +files = [ + {file = "sphinx_click-6.2.0-py3-none-any.whl", hash = "sha256:1fb1851cb4f2c286d43cbcd57f55db6ef5a8d208bfc3370f19adde232e5803d7"}, + {file = "sphinx_click-6.2.0.tar.gz", hash = "sha256:fc78b4154a4e5159462e36de55b8643747da6cda86b3b52a8bb62289e603776c"}, +] + +[package.dependencies] +click = ">=8.0" +docutils = "*" +sphinx = ">=4.0" + +[package.extras] +docs = ["reno"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sqlalchemy" +version = "2.0.51" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0"}, + {file = "sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652"}, + {file = "sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d"}, + {file = "sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84"}, + {file = "sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080"}, + {file = "sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1"}, + {file = "sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86"}, + {file = "sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5"}, + {file = "sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b"}, + {file = "sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8"}, + {file = "sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7"}, + {file = "sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bb1f5062f98b0b3290e72b707747fdd7e0f22d6956b236ba7ca7f5c9971d2da2"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:247acaa29ccef6250dfd6a3eedf8f94ddf23564180a39fe362e32ae9dbdbde46"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c95ef01f53233a305a874a44a63fbfb1d81cd79b49de0f8529b3548cde437e37"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fa268106c8987639a17a18514cfe0cd9bf17420ab887e1e1bf486da8836135b1"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b7f08588854bbb724041d9ae9d980d40040c922382e1d9a2ecb390edc4fd5032"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-win32.whl", hash = "sha256:6b588fd681ddf0c196b8df1ea49a8913514894b2b8f945a9511b4b48871f99c8"}, + {file = "sqlalchemy-2.0.51-cp38-cp38-win_amd64.whl", hash = "sha256:ca216e8af5c05e326efc7e28716ac2381a7cf9791749f5ee1849dccdc99c9b00"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa18ae738b5170e253ad0bb6c4b0f07585081e8a6e50893e4d911d47b39a0904"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59cab3686b1bc039dd9cded2f8d0c08a246e84e76bd4ab5b4f18c7cdae293825"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111604e637da87031255ddc26c7d7bc22bc6af6f5d459ccff3af1b4660233a85"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad30ae663711786303fbcd46a47516302d201ee49a877cb3fac61f672895110a"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b21f0e7efc7a5c509e953784e9d1575ebb8b4318960e7e7d7a93bb803626cf64"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-win32.whl", hash = "sha256:a42ad6afcbaaa777241e347aa2e29155993045a0d6b7db74da61053ffe875fe0"}, + {file = "sqlalchemy-2.0.51-cp39-cp39-win_amd64.whl", hash = "sha256:2a97eaad21c84b4ef8010b11eeba9fe6153eb0b3df3ff8b6abc309df1b978ef7"}, + {file = "sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5"}, + {file = "sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9"}, +] + +[package.dependencies] +greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] +aioodbc = ["aioodbc", "greenlet (>=1)"] +aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (>=1)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "tomli" version = "2.4.1" @@ -1259,6 +2977,26 @@ files = [ {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] +[[package]] +name = "tqdm" +version = "4.68.4" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2"}, + {file = "tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +discord = ["envwrap", "requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["envwrap", "slack-sdk"] +telegram = ["envwrap", "requests"] + [[package]] name = "types-pytz" version = "2026.1.1.20260408" @@ -1281,6 +3019,20 @@ files = [ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "tzdata" version = "2026.2" @@ -1292,6 +3044,20 @@ files = [ {file = "tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10"}, ] +[[package]] +name = "uri-template" +version = "1.3.0" +description = "RFC 6570 URI Template Processor" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, + {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, +] + +[package.extras] +dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-modern-annotations", "flake8-noqa", "flake8-pyproject", "flake8-requirements", "flake8-typechecking-import", "flake8-use-fstring", "mypy", "pep8-naming", "types-PyYAML"] + [[package]] name = "urllib3" version = "2.6.3" @@ -1327,7 +3093,162 @@ platformdirs = ">=3.9.1,<5" python-discovery = ">=1.2.2" typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "webcolors" +version = "25.10.0" +description = "A library for working with the color formats defined by HTML and CSS." +optional = false +python-versions = ">=3.10" +files = [ + {file = "webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d"}, + {file = "webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf"}, +] + +[[package]] +name = "wrapt" +version = "2.2.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.9" +files = [ + {file = "wrapt-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:055e6fcfaa28e58c6a8c247d48b92be9d56f818b7068aa4f22b15b3343a09931"}, + {file = "wrapt-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8374eb6b1a58809211e84ff835a182bb17ab2807a5bfef23204c8cff38178a00"}, + {file = "wrapt-2.2.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:656593bb3f5529f03d27af4136c4d7b11990e470bcbc6fefa5ef218695bece55"}, + {file = "wrapt-2.2.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfb00cb7bb22099e2f64b7340fb96113639aa7260c0972af3797ace2297b936c"}, + {file = "wrapt-2.2.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7f10ee0bd53673bfd52b67cbce83336fe6cad90d2377b03baf66491d2bbfb91"}, + {file = "wrapt-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4402f57c5f0d0579599858ffbdd9bf4e3f0972f51096f2bd6cc7dab6b76ee49e"}, + {file = "wrapt-2.2.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3a4eb7964ff4643d333c84f880bcf554652b2a1050aebc54ae696327f61acfaf"}, + {file = "wrapt-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e542b7c5af91e2123a8aabf19894319d5ec4268d2a9ffd2f239386133fc47746"}, + {file = "wrapt-2.2.2-cp310-cp310-win32.whl", hash = "sha256:6e7e45b43d3c774d244fe7264378f5a3f0f383bc55a54a9866434e524540110f"}, + {file = "wrapt-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:955f1d6e72a352e478de8d8b503abe301c5e139a141b62eb0923bd694995025f"}, + {file = "wrapt-2.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:b89d8d73c82db2bb7e6090b3afd7973f980d24e905cc34394eab60b884b3bf67"}, + {file = "wrapt-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f1a2ff355ece6a111ca7a20dc86df6659c9205d3fcee674ca34f2a2854fd4e73"}, + {file = "wrapt-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55b9a899e6fff5444f229d30aa6e9ac92d2216d9d60f33c771b5d76a760d5f8e"}, + {file = "wrapt-2.2.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a2d78c363f97d8bd718ee40432c66395685e9e98528ccaa423c3355d1715a26d"}, + {file = "wrapt-2.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d619e1eed9bd4f6ed9f24cd61971aa086fa86505289628d464bcf8a2c2e3f328"}, + {file = "wrapt-2.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:518b0c5e323511ec56a38894802ddd5e1222626484e68efe63f201854ad788e5"}, + {file = "wrapt-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4bccea5cdecffa9dd70e343741f0e41e0a16619313d04b72f78bb525162ebcd0"}, + {file = "wrapt-2.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:209112cafd963710a05d199aae431d79a28bc76eb8e6d1bbbb8ad24340722cae"}, + {file = "wrapt-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5a5290e4bf2f332fc29ce72ffb9a2fff678aaac047e2e9f5f7165cd7792e099"}, + {file = "wrapt-2.2.2-cp311-cp311-win32.whl", hash = "sha256:5499236ad1dc116012e2a5dd943f3f31af12fce452128e2bbcbd55a7d3d4d14c"}, + {file = "wrapt-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8636809939152be6ae20a6cef0fed9fe60f411b47847d0426a826884b469e971"}, + {file = "wrapt-2.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:5d0a142f7af07caeb5e5da87493162a7b8efa19ba919e550a746f7446e13fb30"}, + {file = "wrapt-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8417fd3c674d3c8023d080292d29301531a12daf8bd938dd419710dd2f464f2b"}, + {file = "wrapt-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e7070c7472582e31af3dfc2622b2381a0df7435110a9388ed8db5ffbce67efb"}, + {file = "wrapt-2.2.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e096c9d39a59b35b63c9aacfbbbec2088ff51ff1fc31051acc60a07f42f273a"}, + {file = "wrapt-2.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d1a6050405bf334be33bf66296f113563622972a34900ae6fa60fd283a1a900"}, + {file = "wrapt-2.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10adb01371408c6de504a6658b9886480f1a4919a83752748a387a504a21df79"}, + {file = "wrapt-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3442eee2a5798f9b451f1b2cd7518ce8b7e28a2a364696c414460a0e295c012a"}, + {file = "wrapt-2.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6c99012a22f735a85eed7c4b86a3e99c30fdd57d9e115b2b45f796264b58d0bf"}, + {file = "wrapt-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b686cfc008776a3952d6213cb296ed7f45d782a8453936406faa89eac0835ab"}, + {file = "wrapt-2.2.2-cp312-cp312-win32.whl", hash = "sha256:ef2cce266b5b0b07e19fa82e59673b81142b7a3607c8ed1254113d048ed668da"}, + {file = "wrapt-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:abf8c20a2d72ee69e16328b3c91342c446e723bfe48bfcc4dded3b9722ac027f"}, + {file = "wrapt-2.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:c6c64c5d02578bc4c4bca4f0aef1504de933c1d5b4ac2710b9131111459506c8"}, + {file = "wrapt-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e8b648270c613720a202d9a45ebabc33261b22c3a839b115ac5bce8c0bb0d69"}, + {file = "wrapt-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6fb7e94e8fe3e4c3067bb1653a91cce7c5e83acc119fdd41501b1bf74654617"}, + {file = "wrapt-2.2.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb18fc51e813df0d9c98049e3bf2298a5495a648602040e21fa3c7329371159e"}, + {file = "wrapt-2.2.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b00b00f806eb3ef2abe9049ed45994a81ee9284884d96e6b8314927c6cea3d"}, + {file = "wrapt-2.2.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:62415fd095bc590b842b6d092f2b5d9ccbaeb7e0b28535c03dcea2718b48636b"}, + {file = "wrapt-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a41e758d80dc0ab8c210f641ac892009d356cf1f955d97db544c8dd317b4d14c"}, + {file = "wrapt-2.2.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b84cd4058001c9727b0e9980b7a9e66325b5ca748b1b578e822cade1bc6b304f"}, + {file = "wrapt-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26fc73a1b15e0946d2942b9a4426d162b51676338327dc067ccd8d2d76385f94"}, + {file = "wrapt-2.2.2-cp313-cp313-win32.whl", hash = "sha256:3c4095803491f6ef72128914c28ec05bbad9758433bb35f6715a3e9c8e46fb2d"}, + {file = "wrapt-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:2cb07f414fab25dbe6b5c7398e1491423a5c81a6209533639969a6c928d474a4"}, + {file = "wrapt-2.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:1fc7691f070220215cccb2a20836b9adbaecb8ff22ad47abe63de5f110994fac"}, + {file = "wrapt-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ec8f83949028366531383603139403cac7a826e4011955813cdd640017845ce5"}, + {file = "wrapt-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b481fb0c40d9fd90a5809911208da700987d373a20a4709dc9e3944af7a6bec"}, + {file = "wrapt-2.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0065a3b657cec06813b4241d2462ccec287f6863103d7445b725fb3a889736f9"}, + {file = "wrapt-2.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30f7424af5c5c345b7f26490e097f74a2ef45b3d08b664dc33571aee3bd3b56c"}, + {file = "wrapt-2.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07fdcb012821859168641acf68afad61ef9783cf37100af85f152550e9677194"}, + {file = "wrapt-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f90038ab58fafb584801ca62d72384d7d5225d93c76f7b773c22fae545bd8066"}, + {file = "wrapt-2.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c5d7825491bfa2d08b97e9557768987952c7b9ae687d06c3320b40a37ccb7f20"}, + {file = "wrapt-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ad520e6daa9bbf136f14de735474dbec7dcc0891f718e1d274ce8dc92e645af"}, + {file = "wrapt-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:25904acb9475f46c24fe0423dbc8fda8cc5fbc282ab3dc6e72e919748c53f4e9"}, + {file = "wrapt-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:305d4c247d61c4115794a169141823c62f719525ddb90b23aa332741c77d2c28"}, + {file = "wrapt-2.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c20279cd1a29800815d7b2d6338b60a6c6e78263f9d6e62e0eda251ba9cae2d0"}, + {file = "wrapt-2.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0e64826f920c42d9d9f87e8cc09ffae66c51ede12d59061a5a426deb9aa71745"}, + {file = "wrapt-2.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcaa5e1451bd8751d7bd1568dfa3321c78092a52a7ecb5d1a0f18a5791e1fd00"}, + {file = "wrapt-2.2.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0abfd648dac9ac9c5b3aa9b523d27f1789046640b58dcd5652a720ddb325e1fc"}, + {file = "wrapt-2.2.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4bfd8d1eb438153eff8b8cfe87f032ba65731e1ce06138b5090f745a33f6f95"}, + {file = "wrapt-2.2.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c427c9d06d859848a69f0d928fe28b5c33a941b2265d10a0e1f15cd244f1ee33"}, + {file = "wrapt-2.2.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4250b43d1a129d947e083c4dc6baf333c9bb34edd26f912d5b0457841fc858ab"}, + {file = "wrapt-2.2.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:173e5bb5ca350a6e0abab60b7ec7cdd7992a814cb14b4de670a28f067f105663"}, + {file = "wrapt-2.2.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa14b01804bce36c6d63d7b6a4f55df390f29f8648cc13a1f40b166f4d54680d"}, + {file = "wrapt-2.2.2-cp314-cp314-win32.whl", hash = "sha256:58f9f8d637c9a6e245c6ef5b109b67ec187d2faed23d1405656b51d96e0a5b56"}, + {file = "wrapt-2.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:385cb1866f20479e83299af585375bfa0a4b0c6c9907a981483ea782ea8ae406"}, + {file = "wrapt-2.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:8ffbeaea6771a6eba6e6eeb09767864995726bc8240bb54baf88a9bb1db34d5c"}, + {file = "wrapt-2.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:09f811d43f6f33ec7515f0be76b159569f4057ab54d3e079c3204dddb90afa2a"}, + {file = "wrapt-2.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a795d3c06e5fbf9ea2f13196180b77aeab1b4685917256ee0d014cc163d90063"}, + {file = "wrapt-2.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:45c2f2768e790c9f8db90f239ef23a2af8e7570f25a35619ef902df4a738447f"}, + {file = "wrapt-2.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbf00ee0cb55ec24e2b0995a71942b85b21a066db8f3f46e1dbfdb9433ffba81"}, + {file = "wrapt-2.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2252f77663651b89255895f58cc6ac08fcb206d4371813e5af61bb62d4f7689c"}, + {file = "wrapt-2.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cd7181ab1c31192ff5219269830744b5a62020b3a6d433588c4f1c95b8f8bff"}, + {file = "wrapt-2.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6fe35fd51b74867d8b80174c277bd6bbf6a73e443f908129dc531c4b688a20d5"}, + {file = "wrapt-2.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11d95fc2fbad3163596c39d440e6f21ca9fccece74b56e30a37ac2fca786a07c"}, + {file = "wrapt-2.2.2-cp314-cp314t-win32.whl", hash = "sha256:d8a15813215f33fa83667bfc978b300e35669ea8bb424e970a1426bcb7bc6cca"}, + {file = "wrapt-2.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d09db0f7e8357060d3c38fc22a018aba683a796bf184360fd1a58f6fc180dc77"}, + {file = "wrapt-2.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f32fe639c39561ccc187bcae17e9271be0eb45f1c2952510d2f29b33ab577347"}, + {file = "wrapt-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d01d8e0afc55823245a3b97a79c7c77464e31ea7a7b629a4bf26f9441dc1f18e"}, + {file = "wrapt-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a28287413351cb198b8c5ddd045c56fac1d195808642cd264d1ab50426146650"}, + {file = "wrapt-2.2.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:abf033b7e4542357659cd83ed6cd5033c43aaa1887044045ceb571528837f72f"}, + {file = "wrapt-2.2.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d2f6573561fa05002e5ee71529f4ab0a7dffed3e45b51013fe6298fe2723c02"}, + {file = "wrapt-2.2.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c38510a21d5b9cf3e84c460d909e9f2a098667439fd42841bb081cab45835d68"}, + {file = "wrapt-2.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cd385a48b055bdc3630ab30e0c7fd8514a36904ec23f9cee7a65d887334a3cea"}, + {file = "wrapt-2.2.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:3dc3dcfc2da95d501905f10dc11a0dc622e91d8cdd8bbfcb63ca54afd131e556"}, + {file = "wrapt-2.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fa81c5b5fe8cd6c41e3a798533b81288279e5fdbde2128f21071922764281c99"}, + {file = "wrapt-2.2.2-cp39-cp39-win32.whl", hash = "sha256:814f1bf3e0a7035f67a1db0cdaf5e2bbcaa4d7092db96673cfa467adeaab8591"}, + {file = "wrapt-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:9ee098171b07edba66ab69a9bf0251d3cbef654107e800feb24c0c6f30592728"}, + {file = "wrapt-2.2.2-cp39-cp39-win_arm64.whl", hash = "sha256:3179a4db066b53d40562e368b12895440c8f0953b6543b89d6acc41c0273996e"}, + {file = "wrapt-2.2.2-py3-none-any.whl", hash = "sha256:5bad217350f19ce99ca5b5e71d406765ea86fe541628426772b657375ee1c048"}, + {file = "wrapt-2.2.2.tar.gz", hash = "sha256:0788e321027c999bf221b667bd4a54aaefd1a36283749a860ac3eb77daed0302"}, +] + +[package.extras] +dev = ["pytest", "setuptools"] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "9fed2e8fc38b31a4085ceee8b6d5653ce1e2354569b75ccda80302b29993a28a" +content-hash = "f44606dc93e69254e9342b69b964bee02499d72f5fcab2f95daa23e3455954f9" diff --git a/pyproject.toml b/pyproject.toml index d2a7ff4..7e6d4f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ ruff = "^0.3.4" pre-commit = "^3.7.0" pytest = "^9.0.3" pytest-cov = "^7.0.0" +linkml = "^1.11.1" [build-system] requires = ["poetry-core"] @@ -40,6 +41,12 @@ plugins = ["flake8-mypy"] line-length = 88 # Adjust line length as needed target-version = ['py39'] +[tool.ruff.lint] +# The codebase uses one-line guard/assignment statements pervasively +# (`if cond: continue`, `a = x; b = y`). Those stylistic rules (E701/E702) are +# ignored to match existing style; real issues (F, other E) stay enforced. +ignore = ["E701", "E702"] + [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] diff --git a/schema/logic_network.linkml.yaml b/schema/logic_network.linkml.yaml new file mode 100644 index 0000000..6fa5649 --- /dev/null +++ b/schema/logic_network.linkml.yaml @@ -0,0 +1,238 @@ +id: https://reactome.org/logic-network-generator/schema/logic_network +name: logic_network +title: Reactome Logic Network Output Schema +description: >- + Machine-readable contract for the files the logic-network-generator (LNG) + emits for a single Reactome pathway. These files are consumed by deltasignal + (signal-propagation solver), the benchmark harness (mapping to experimental / + curator tests), and the web UI (overlaying results on Reactome pathway + diagrams). + + The network is a signed logic graph over UUID-keyed NODES. A node is one + biological entity as it appears at a position in the pathway: a simple entity + (protein / small molecule), a bundled complex, a set-VARIANT of a complex + (the complex with one specific choice per internal EntitySet), or a synthetic + boundary node (assembly leaf / dissociation sink) or catalyst/regulator + variant. Complexes are NEVER broken into their individual members; they are + only split along their internal EntitySets ("break apart by sets"), which is + what set_variant nodes record. + + Provenance is normalized by grain: intrinsic 1:1 / fixed-multivalued node + attributes live on Node; the independent many-to-many Node<->reaction + relationship (with its own `role`) lives in NodeReactionContext. +license: Apache-2.0 +default_range: string + +prefixes: + linkml: https://w3id.org/linkml/ + lng: https://reactome.org/logic-network-generator/schema/ + reactome: https://reactome.org/content/detail/ +default_prefix: lng + +imports: + - linkml:types + +# --------------------------------------------------------------------------- +# Enums +# --------------------------------------------------------------------------- +enums: + NodeKind: + description: What a logic-network node represents. + permissible_values: + simple_entity: + description: A single protein, small molecule, or other atomic entity. + simple_complex: + description: A Complex with no internal EntitySet — one bundled node. + set_variant: + description: >- + A Complex containing one or more EntitySets, resolved to ONE specific + choice per set. diagram_entity_id is the parent complex; chosen_members + / source_sets record the choice. + dissociation_sink: + description: >- + Synthetic per-occurrence readout node for a member released from a + terminal-output complex. Has incoming edges, no outgoing edges. + assembly_leaf: + description: >- + Synthetic shared handle for a member feeding a root-input complex via + an assembly edge. + regulator_variant: + description: A set-variant produced by catalyst / negative-regulator decomposition. + reaction: + description: A virtual-reaction node (edges route input->reaction->output). + other: + description: Fallback for anything not otherwise classified. + + EdgeType: + description: The biological meaning of an edge. + permissible_values: + input: {description: Entity consumed by a reaction.} + output: {description: Entity produced by a reaction.} + catalyst: {description: Catalyst that accelerates a reaction.} + regulator: {description: Positive or negative regulator of a reaction.} + depletion: {description: Catalyst -> substrate; catalyst removes its substrate (divide-form inhibition).} + assembly: {description: Member -> complex; the complex requires the member (root-input boundary).} + dissociation: {description: Complex -> released member readout sink (terminal-output boundary).} + handoff: {description: "Producer output -> consumer input across a curated precedingEvent link where they share a component carrier (bundling would otherwise drop the connection). Positive OR activator."} + + Role: + description: >- + How a node participates in a reaction. Regulator sign (positive vs + negative) is carried by the corresponding edge's pos_neg; this location + table uses the generic ``regulator`` role. + permissible_values: + input: {} + output: {} + catalyst: {} + regulator: {} + positive_regulator: {} + negative_regulator: {} + + PosNeg: + permissible_values: + pos: {description: Activating / positive influence.} + neg: {description: Inhibiting / negative influence.} + + AndOr: + permissible_values: + and: {description: All inputs required (AND aggregation).} + or: {description: Any input suffices (OR aggregation).} + +# --------------------------------------------------------------------------- +# Slots +# --------------------------------------------------------------------------- +slots: + uuid: + description: Stable UUID identifying a node within this pathway's logic network. + identifier: true + range: string + node_kind: + range: NodeKind + required: true + diagram_entity_id: + description: >- + The Reactome stable ID a pathway diagram renders for this node — the key + for UI overlay and for collapsing per-UUID results to per-Reactome-ID + values. For a set_variant this is the PARENT complex stId; for a simple + entity/complex it is the entity's own stId; for a reaction it is the + reaction stId. + range: string + required: true + compartment: + description: Reactome compartment display name, if known. + range: string + required: false + member_leaves: + description: >- + Terminal leaf entity stIds (genes / proteins / small molecules) contained + in this node. Enables gene-knockout -> node mapping for the tests without + parsing the node id. Empty for atomic simple entities and reactions. + range: string + multivalued: true + required: false + source_sets: + description: EntitySet stIds this node was split on (empty unless a set was resolved). + range: string + multivalued: true + required: false + chosen_members: + description: The specific set-member stId(s) selected in this set_variant. + range: string + multivalued: true + required: false + + # NodeReactionContext slots + context_node: + description: The node participating in the reaction. + range: Node + required: true + reaction_id: + description: Reactome stable ID of the reaction (ReactionLikeEvent). + range: string + required: true + role: + range: Role + required: true + + # Edge slots + source_id: + range: Node + required: true + target_id: + range: Node + required: true + pos_neg: + range: PosNeg + required: true + and_or: + range: AndOr + required: false + edge_type: + range: EdgeType + required: true + stoichiometry: + range: integer + required: false + edge_reaction_id: + description: >- + Reactome reaction this edge was derived from (null for synthetic boundary + edges). Lets the UI colour reactions and identifies where reactions were + split by sets. + range: string + required: false + +# --------------------------------------------------------------------------- +# Classes (one class per output table + a container) +# --------------------------------------------------------------------------- +classes: + Node: + description: One node of the logic network. Serialized as a row in nodes.csv. + slots: + - uuid + - node_kind + - diagram_entity_id + - compartment + - member_leaves + - source_sets + - chosen_members + + NodeReactionContext: + description: >- + A (node, reaction, role) participation. Serialized as a row in + node_reaction_context.csv. Many rows per node (a merged UUID appears in + several reactions); this is the location layer for diagram placement. + slots: + - context_node + - reaction_id + - role + + Edge: + description: One signed logic edge. Serialized as a row in edges.csv (logic_network.csv). + slots: + - source_id + - target_id + - pos_neg + - and_or + - edge_type + - stoichiometry + - edge_reaction_id + + LogicNetwork: + description: Container for one pathway's logic network (tree root for validation). + tree_root: true + attributes: + pathway_id: + description: Reactome stable ID of the pathway this network was generated for. + range: string + nodes: + range: Node + multivalued: true + inlined_as_list: true + node_reaction_contexts: + range: NodeReactionContext + multivalued: true + inlined_as_list: true + edges: + range: Edge + multivalued: true + inlined_as_list: true diff --git a/src/diagram_connectivity.py b/src/diagram_connectivity.py new file mode 100644 index 0000000..f3399f8 --- /dev/null +++ b/src/diagram_connectivity.py @@ -0,0 +1,164 @@ +"""Reaction connectivity from Reactome diagram JSON. + +The generator connects two reactions only when the curator annotated a +``precedingEvent`` between them. Older pathways under-annotate ``precedingEvent``, +so reactions where A's product is literally B's substrate are left disconnected. + +The pathway **diagram** is the curator's drawn connectivity: two reactions are +linked when they share an entity **glyph** (A's output glyph == B's input/catalyst +glyph). We extract those (producer, consumer) reaction pairs and feed them into +``reaction_connections`` alongside ``precedingEvent`` — Phase 2 then merges the +shared product into one node, giving ``A -> product -> B``. + +Why the diagram beats raw Neo4j input/output matching: curators draw cofactors +(ATP/ADP/H2O/etc.) as **separate per-reaction glyphs**, so shared-glyph +connectivity excludes cofactors for free — no hub/threshold tuning. + +See reactome/logic-network-generator#39. +""" +import json +import os +from collections import defaultdict +from pathlib import Path +from typing import Set, Tuple + +import pandas as pd + +from src.argument_parser import logger + + +def _diagram_dir() -> Path: + return Path(os.environ.get("LNG_DIAGRAM_DIR", os.path.expanduser("~/reactome-diagrams/97"))) + + +def _has_diagram(stid: str) -> bool: + d = _diagram_dir() + return (d / f"{stid}.json").exists() and (d / f"{stid}.graph.json").exists() + + +def _covering_diagram_stid(pathway_id: str) -> str: + """stId of the diagram that renders this pathway. + + We generate mostly top-level pathways (which have their own diagram), but the + TEST pathways are often sub-pathways that inherit an ancestor's diagram. Use + the pathway's own diagram if present, else the nearest diagrammed ancestor + (walking up ``hasEvent`` in Neo4j). Returns "" if none found. + """ + if _has_diagram(pathway_id): + return pathway_id + from src.neo4j_connector import get_graph + ancestors = get_graph().run( + """MATCH path=(anc:Pathway)-[:hasEvent*]->(p:Pathway {stId:$pid}) + RETURN anc.stId AS stid, length(path) AS d ORDER BY d""", + pid=pathway_id, + ).data() + for a in ancestors: + if _has_diagram(a["stid"]): + return a["stid"] + return "" + + +def _pathway_reaction_stids(pathway_id: str) -> Set[str]: + """All ReactionLikeEvent stIds contained in the pathway (to isolate it within + an ancestor diagram).""" + from src.neo4j_connector import get_graph + rows = get_graph().run( + "MATCH (p:Pathway {stId:$pid})-[:hasEvent*]->(r:ReactionLikeEvent) " + "RETURN collect(DISTINCT r.stId) AS r", + pid=pathway_id, + ).evaluate() + return set(rows or []) + + +def diagram_shared_product_pairs(pathway_id: str) -> Set[Tuple[str, str]]: + """(producer_stId, consumer_stId) reaction pairs drawn as connected in the diagram. + + A pair is emitted when a producer reaction's OUTPUT glyph is the same glyph a + consumer reaction takes as INPUT or CATALYST. Reaction identity is the + Reactome stId (mapped from the diagram's numeric reactomeId via the + companion ``.graph.json``). The diagram used is the pathway's own, else the + nearest diagrammed ancestor; pairs are then **restricted to reactions that + belong to this pathway** so an ancestor diagram only contributes the target + pathway's internal connectivity. Returns an empty set if no diagram covers it. + """ + ddir = _diagram_dir() + diagram_stid = _covering_diagram_stid(pathway_id) + if not diagram_stid: + logger.info(f"No diagram (own or ancestor) covers {pathway_id}; skipping diagram connectivity") + return set() + if diagram_stid != pathway_id: + logger.info(f"{pathway_id} has no own diagram; using ancestor diagram {diagram_stid}") + + layout = json.loads((ddir / f"{diagram_stid}.json").read_text()) + graph = json.loads((ddir / f"{diagram_stid}.graph.json").read_text()) + # Restrict to this pathway's reactions (isolate it within the ancestor diagram). + own_reactions = _pathway_reaction_stids(pathway_id) + + # reaction dbId -> stId (graph.json edges carry both) + dbid_to_stid = {e["dbId"]: e["stId"] for e in graph.get("edges", []) if e.get("stId")} + + # For each reaction glyph (diagram 'edge'): its output glyph ids, and its + # input+catalyst glyph ids. reactomeId on a diagram edge is the reaction dbId. + glyph_out_rxns = defaultdict(set) # glyph_id -> {reaction_dbId producing it} + glyph_in_rxns = defaultdict(set) # glyph_id -> {reaction_dbId consuming it} + for e in layout.get("edges", []): + rdb = e.get("reactomeId") + if rdb is None: + continue + for x in e.get("outputs", []): + glyph_out_rxns[x["id"]].add(rdb) + for role in ("inputs", "catalysts"): + for x in e.get(role, []): + glyph_in_rxns[x["id"]].add(rdb) + + pairs: Set[Tuple[str, str]] = set() + for glyph_id in set(glyph_out_rxns) & set(glyph_in_rxns): + for producer in glyph_out_rxns[glyph_id]: + for consumer in glyph_in_rxns[glyph_id]: + if producer == consumer: + continue + p_st = dbid_to_stid.get(producer) + c_st = dbid_to_stid.get(consumer) + if not p_st or not c_st: + continue + # Keep only pairs whose BOTH reactions belong to this pathway + # (so an ancestor diagram contributes only this pathway's flow). + if own_reactions and (p_st not in own_reactions or c_st not in own_reactions): + continue + pairs.add((p_st, c_st)) + return pairs + + +def augment_reaction_connections(pathway_id: str, + reaction_connections: pd.DataFrame) -> pd.DataFrame: + """Union diagram-drawn product->substrate pairs into reaction_connections. + + Adds only pairs not already present (as preceding->following). Tagged + event_status='Diagram Shared Product' for traceability. No-op (returns input + unchanged) when disabled or no diagram is available. + """ + if os.environ.get("LNG_DIAGRAM_CONNECTIVITY", "1") == "0": + return reaction_connections + + pairs = diagram_shared_product_pairs(pathway_id) + if not pairs: + return reaction_connections + + existing = set( + zip(reaction_connections.get("preceding_reaction_id", pd.Series(dtype=str)), + reaction_connections.get("following_reaction_id", pd.Series(dtype=str))) + ) + new_rows = [ + {"preceding_reaction_id": p, "following_reaction_id": c, + "event_status": "Diagram Shared Product"} + for (p, c) in pairs if (p, c) not in existing + ] + if not new_rows: + logger.info(f"Diagram connectivity for {pathway_id}: all {len(pairs)} pairs already linked") + return reaction_connections + + logger.info( + f"Diagram connectivity for {pathway_id}: +{len(new_rows)} product->substrate " + f"pairs not in precedingEvent (of {len(pairs)} drawn)" + ) + return pd.concat([reaction_connections, pd.DataFrame(new_rows)], ignore_index=True) diff --git a/src/logic_network_generator.py b/src/logic_network_generator.py index 44d88cd..0674719 100755 --- a/src/logic_network_generator.py +++ b/src/logic_network_generator.py @@ -1,5 +1,6 @@ +import os import uuid -from typing import Dict, List, Any, NamedTuple, Optional, Set +from typing import Dict, List, Any, NamedTuple, Optional, Set, Tuple import pandas as pd from pandas import DataFrame @@ -26,6 +27,7 @@ class PathwayResult(NamedTuple): uuid_mapping: Dict[str, str] catalyst_regulator_map: pd.DataFrame reaction_id_map: pd.DataFrame + entity_uuid_registry: Dict[tuple, str] = {} def _get_reactome_id_from_hash(decomposed_uid_mapping: pd.DataFrame, hash_value: str) -> str: @@ -487,43 +489,193 @@ def _build_reactome_to_vr_map(reaction_id_map: pd.DataFrame) -> Dict[str, List[s return reactome_to_vr +def _parse_variant_members(variant_id: str) -> Set[str]: + """Terminal member stIds encoded in a ``{parent}::variant::{m1_m2}`` id.""" + if "::variant::" not in variant_id: + return set() + tail = variant_id.split("::variant::", 1)[1] + return {m for m in tail.split("_") if m} + + +_variant_leafsets_cache: Dict[str, List[frozenset]] = {} + + +def _complex_variant_leafsets(complex_id: str) -> List[frozenset]: + """Enumerate a complex's set-variants as FULL terminal-leaf sets. + + One frozenset per variant, each the complete terminal membership of that + variant (fixed components + one choice per internal EntitySet + recursively + for nested complexes). Unlike :func:`_expand_complex_variants` this returns + flat leaf sets (no nested ``::variant::`` ids), which is what the emission + node id and the member-set matching need. + """ + import itertools + from src.neo4j_connector import get_labels, get_complex_components, get_set_members + + if complex_id in _variant_leafsets_cache: + return _variant_leafsets_cache[complex_id] + + components = get_complex_components(complex_id) + if not components: + result = [frozenset(get_terminal_components(complex_id))] + _variant_leafsets_cache[complex_id] = result + return result + + per_component_choices: List[List[frozenset]] = [] + for member_id in components: + labels = get_labels(member_id) + if "Complex" in labels and _complex_contains_entity_set(member_id): + per_component_choices.append(_complex_variant_leafsets(member_id)) + elif ( + any(lbl in labels for lbl in ("EntitySet", "DefinedSet", "CandidateSet")) + and member_id not in _UBIQUITIN_ENTITY_SET_IDS + ): + choices = [frozenset(get_terminal_components(sm)) for sm in get_set_members(member_id)] + per_component_choices.append(choices or [frozenset(get_terminal_components(member_id))]) + else: + per_component_choices.append([frozenset(get_terminal_components(member_id))]) + + variants: List[frozenset] = [] + seen: Set[frozenset] = set() + for combo in itertools.product(*per_component_choices): + leaves = frozenset().union(*combo) if combo else frozenset() + if leaves and leaves not in seen: + seen.add(leaves) + variants.append(leaves) + result = variants or [frozenset(get_terminal_components(complex_id))] + _variant_leafsets_cache[complex_id] = result + return result + + +def _map_annotated_entity_to_nodes(entity_id: str, member_set: Set[str]) -> Set[str]: + """Map one reaction-annotated input/output entity to its emission node(s). + + This is where "a complex is a single node, split only by its internal sets" + is enforced — at *emission* time, using the reaction's real annotated + entities (unambiguous), not the content-addressed matching hashes. + + * simple entity / simple complex → the entity's own stId (one bundled node) + * complex-that-contains-a-set → the specific set-VARIANT node selected by + this virtual reaction, named ``{complex}::variant::{sorted members}``. + The variant is chosen by matching its members against ``member_set`` (the + terminal members this VR actually resolved to), so we pick the right + alternative rather than emitting all of them. + * bare EntitySet → the terminal member(s) present in this VR (sets expand) + """ + from src.neo4j_connector import get_labels + labels = get_labels(entity_id) + + if "Complex" in labels: + if not _complex_contains_entity_set(entity_id): + return {str(entity_id)} # simple complex → single bundled node + # Set-variant node: OUTERMOST complex stId + a FLAT sorted list of the + # variant's FULL terminal membership. We enumerate the complex's true + # variants (each with complete membership) and pick the one this VR + # selected — the variant whose leaves are all present in member_set + # (largest such, to prefer the fullest match). Enumerating true variants + # (rather than intersecting all-possible-leaves with member_set) avoids + # emitting spurious partial variants missing a subunit. Id is kept flat + # so the parent is always ``id.split("::variant::")[0]``. + variant_leafsets = _complex_variant_leafsets(entity_id) + subset = [ls for ls in variant_leafsets if ls <= member_set] + if subset: + chosen = max(subset, key=len) + else: + chosen = max(variant_leafsets, key=lambda ls: len(ls & member_set), + default=frozenset()) + if not (chosen & member_set): + return {str(entity_id)} # no fit → fall back to plain complex + return {f"{entity_id}::variant::{'_'.join(sorted(chosen))}"} + + if any(lbl in labels for lbl in ("EntitySet", "DefinedSet", "CandidateSet")): + if entity_id in _UBIQUITIN_ENTITY_SET_IDS: + return {str(entity_id)} + present = get_terminal_components(entity_id) & member_set + return present if present else {str(entity_id)} + + return {str(entity_id)} # simple entity (protein / small molecule / …) + + def _resolve_vr_entities( reaction_id_map: pd.DataFrame, uid_index: Dict[str, tuple] ) -> Dict[str, tuple]: - """Resolve each virtual reaction's input/output hashes to terminal Reactome IDs. + """Resolve each virtual reaction's inputs/outputs to emission NODES. - Caches the resolution so Phase 2 and Phase 3 don't re-resolve. + A node is a bundled complex (or set-variant of one), a bare-set member, or a + free simple entity — NOT the complex's individual member proteins. The + matching layer (content hashes) is used only to learn which terminal members + this VR selected; the node *identities* come from the reaction's real + annotated entities via :func:`_map_annotated_entity_to_nodes`, which keeps + parent-complex provenance unambiguous (content hashes collide across + complexes with identical member content). - Args: - reaction_id_map: DataFrame with 'uid', 'input_hash', 'output_hash' columns - uid_index: Pre-built lookup index from _build_uid_index + Caches the resolution so Phase 2 and Phase 3 don't re-resolve. Returns: - Dict mapping vr_uid -> (input_reactome_ids, output_reactome_ids, + Dict mapping vr_uid -> (input_node_ids, output_node_ids, input_stoich_map, output_stoich_map) - where stoich maps are Dict[str, int] mapping entity_id → stoichiometry """ + from src.neo4j_connector import get_reaction_input_output_ids + + annotated_cache: Dict[tuple, Set[str]] = {} + + def _annotated(reaction_id: str, io: str) -> Set[str]: + key = (reaction_id, io) + if key not in annotated_cache: + annotated_cache[key] = set(get_reaction_input_output_ids(reaction_id, io)) + return annotated_cache[key] + vr_entities: Dict[str, tuple] = {} for _, row in reaction_id_map.iterrows(): vr_uid = row["uid"] - input_stoich = _resolve_to_terminal_reactome_ids(uid_index, row["input_hash"]) - output_stoich = _resolve_to_terminal_reactome_ids(uid_index, row["output_hash"]) - input_ids = list(input_stoich.keys()) - output_ids = list(output_stoich.keys()) - vr_entities[vr_uid] = (input_ids, output_ids, input_stoich, output_stoich) + reaction_id = str(row["reactome_id"]) + input_members = set(_resolve_to_terminal_reactome_ids(uid_index, row["input_hash"])) + output_members = set(_resolve_to_terminal_reactome_ids(uid_index, row["output_hash"])) + + input_ids: Set[str] = set() + for e in _annotated(reaction_id, "input"): + input_ids |= _map_annotated_entity_to_nodes(str(e), input_members) + output_ids: Set[str] = set() + for e in _annotated(reaction_id, "output"): + output_ids |= _map_annotated_entity_to_nodes(str(e), output_members) + + vr_entities[vr_uid] = ( + list(input_ids), list(output_ids), + {n: 1 for n in input_ids}, {n: 1 for n in output_ids}, + ) return vr_entities -def _decompose_regulator_entity(entity_id: str) -> List[tuple]: +def _decompose_regulator_entity( + entity_id: str, + variant_decomposition: bool = False, +) -> List[tuple]: """Decompose a catalyst/regulator entity to (terminal_id, stoichiometry) pairs. - The decomposition rules mirror break_apart_entity (matching layer): - Complex with EntitySet → cartesian over members; simple Complex - returned intact; EntitySet → flat alternatives; ubiquitin sets are - treated as atomic to avoid combinatorial explosion. AND/OR semantics - for the resulting edges are decided by append_regulators based on - pos_neg, not by within-entity decomposition shape. + Two decomposition modes: + + **subunit decomposition** (default, used for catalysts and positive + regulators): Complex with EntitySet → cartesian over members down to + terminal proteins; simple Complex returned intact; EntitySet → flat + alternatives. Each terminal subunit becomes its own edge — biologically + appropriate for catalysts where every subunit of the holoenzyme is + required (AND). + + **variant decomposition** (``variant_decomposition=True``, used for + negative regulators): Complex with EntitySet → one entity per cartesian + variant of the EntitySet expansion, but the complex itself is preserved + (NOT broken into individual proteins). A bare EntitySet expands to its + member alternatives. This is what you want for inhibitors, because: + the inhibitor complex acts as a single biological unit; breaking it + into individual proteins (HSP90, CDC37, etc.) would make those + bystander proteins act as standalone inhibitors, which spuriously + crushes downstream signal whenever unrelated reactions produce them. + + Ubiquitin sets are treated as atomic in both modes to avoid + combinatorial explosion. AND/OR semantics for the resulting edges are + decided by ``append_regulators`` based on pos_neg, not by within-entity + decomposition shape. """ from src.neo4j_connector import get_labels, get_complex_components, get_set_members @@ -532,10 +684,14 @@ def _decompose_regulator_entity(entity_id: str) -> List[tuple]: if "Complex" in labels: if not _complex_contains_entity_set(entity_id): return [(entity_id, 1)] + if variant_decomposition: + return _expand_complex_variants(entity_id) components = get_complex_components(entity_id) # Dict[str, int] result = [] for member_id, stoich in components.items(): - for mid, sub_stoich in _decompose_regulator_entity(member_id): + for mid, sub_stoich in _decompose_regulator_entity( + member_id, variant_decomposition=variant_decomposition + ): result.append((mid, stoich * sub_stoich)) return result if result else [(entity_id, 1)] @@ -545,50 +701,519 @@ def _decompose_regulator_entity(entity_id: str) -> List[tuple]: members = get_set_members(entity_id) result = [] for member_id in members: - result.extend(_decompose_regulator_entity(member_id)) + result.extend( + _decompose_regulator_entity( + member_id, variant_decomposition=variant_decomposition + ) + ) return result if result else [(entity_id, 1)] return [(entity_id, 1)] +def _expand_complex_variants(complex_id: str) -> List[tuple]: + """Expand a Complex-with-EntitySet into its cartesian-product variants. + + Each variant is itself a complex (same proteins, one specific choice + per internal EntitySet), kept as a single biological entity. Returns + [(variant_id, 1), ...] where each variant_id is a deterministic + synthetic ID of the form ``{parent_stid}::variant::{sorted_member_ids}``. + The synthetic ID is content-addressed: the same combination of leaf + members under the same parent complex always produces the same ID, so + cross-pathway references to "the same variant" are consistent. + + For a simple Complex (no EntitySet inside) the input is returned + unchanged — caller already short-circuits on this case, but we + re-check here for safety. + """ + import itertools + from src.neo4j_connector import get_labels, get_complex_components, get_set_members + + if not _complex_contains_entity_set(complex_id): + return [(complex_id, 1)] + + components = get_complex_components(complex_id) + if not components: + return [(complex_id, 1)] + + # For each component, collect the list of identities it can take in a + # single variant. A simple member contributes [member_id]; an internal + # EntitySet contributes [alt_1, alt_2, ...]; a nested Complex (rare) + # contributes its own variant IDs. + per_component_choices: List[List[str]] = [] + for member_id, _stoich in components.items(): + labels = get_labels(member_id) + if "Complex" in labels: + sub_variants = _expand_complex_variants(member_id) + per_component_choices.append([vid for vid, _ in sub_variants]) + elif ( + ("EntitySet" in labels or "DefinedSet" in labels or "CandidateSet" in labels) + and member_id not in _UBIQUITIN_ENTITY_SET_IDS + ): + alts: List[str] = [] + for set_member in get_set_members(member_id): + set_member_labels = get_labels(set_member) + if ( + "Complex" in set_member_labels + and _complex_contains_entity_set(set_member) + ): + alts.extend(vid for vid, _ in _expand_complex_variants(set_member)) + else: + alts.append(set_member) + per_component_choices.append(alts if alts else [member_id]) + else: + per_component_choices.append([member_id]) + + variants: List[tuple] = [] + seen_variant_ids: set = set() + for combo in itertools.product(*per_component_choices): + combo_sorted = sorted(combo) + variant_id = f"{complex_id}::variant::{'_'.join(combo_sorted)}" + if variant_id in seen_variant_ids: + continue + seen_variant_ids.add(variant_id) + variants.append((variant_id, 1)) + + return variants if variants else [(complex_id, 1)] + + +_COFACTOR_STIDS: frozenset = frozenset({ + "R-ALL-113592", # ATP + "R-ALL-29358", # ATP variant + "R-ALL-113582", # ADP + "R-ALL-29370", # ADP variant + "R-ALL-29360", # ADP variant + "R-ALL-29356", # H2O + "R-ALL-29372", # Pi + "R-ALL-29390", # Pi variant + "R-ALL-29438", # PPi + "R-ALL-217093", # NADP+ + "R-ALL-110114", # NADPH + "R-ALL-29986", # NAD+ + "R-ALL-73473", # NADH +}) + +# Ubiquitin entity stIds (human + cross-species variants). A reaction that +# takes one of these as INPUT is a ubiquitination reaction (Ub is consumed +# and attached to a target protein). Reactions whose OUTPUT is Ub are +# deubiquitinations (we don't emit depletion for those). +_UBIQUITIN_STIDS: frozenset = frozenset({ + "R-HSA-68524", # Ub [nucleoplasm] + "R-HSA-113595", # Ub [cytosol] + "R-HSA-9660007", # Ub [lysosomal lumen] +}) + + +def _emit_substrate_depletion_edges( + pathway_logic_network_data: List[Dict[str, Any]], + reactome_id_to_uuid: Dict[str, str], + catalyst_map: pd.DataFrame, +) -> None: + """Emit catalyst→input "depletion" edges for PHOSPHATASE reactions only. + + The biology to capture: when a catalyst REMOVES its substrate from the + available pool, the substrate's level should respond to the catalyst's + activity. PTEN dephosphorylates PIP3 → PTEN-KO needs to BOOST PIP3 in + the propagator (de-repression via divide-form H on the depletion edge). + + SELECTION CRITERION: the reaction's OUTPUTS include Pi (R-ALL-29372). + Cleanly identifies dephosphorylation reactions (PTEN, PTPN12, PHLPP, + PP1, PP2A, etc.) without name heuristics. The free substrate is the + direct input, so catalyst→input depletion targets the right node. + + NOTE: ubiquitin-ligase reactions look superficially similar but + Reactome models them more precisely — the "MDM2 ubiquitinates TP53" + reaction takes the ASSEMBLED `p-MDM2:MDM4:TP53` complex as input, not + free TP53. So a catalyst→input depletion edge points MDM2 → complex, + not MDM2 → TP53, and the heuristic doesn't help (empirically -0.5pp + on the experimental benchmark). Capturing ubiquitin-driven depletion + would require modeling the multi-step bind→ubiquitinate→degrade chain. + + The deltasignal solver applies divide-form inhibition to depletion edges + specifically (DS_DEPLETION_H_MAX caps the de-repression boost, default + 10). Regular regulator edges stay on devspec (no false boost). + + Cofactor inputs (ATP/ADP/H2O/Pi/etc.) are excluded. + """ + # Build reaction_uuid → list of input edges + list of catalyst edges + # + list of output edges (to detect phosphatases by Pi output). + by_target_inputs: Dict[str, List[str]] = {} + by_target_catalysts: Dict[str, List[str]] = {} + by_source_outputs: Dict[str, List[str]] = {} # reaction_uuid → output stids + PI_STID = "R-ALL-29372" # inorganic phosphate + for edge in pathway_logic_network_data: + if edge.get("pos_neg") != "pos": + continue + et = edge.get("edge_type", "") + if et == "input": + by_target_inputs.setdefault(edge["target_id"], []).append(edge["source_id"]) + elif et == "catalyst": + by_target_catalysts.setdefault(edge["target_id"], []).append(edge["source_id"]) + elif et == "output": + # source is the reaction, target is the output entity + by_source_outputs.setdefault(edge["source_id"], []).append(edge["target_id"]) + + # Identify phosphatase reactions: those whose outputs include Pi (R-ALL-29372). + phosphatase_rxn_uuids = set() + for rxn_uuid, output_uuids in by_source_outputs.items(): + output_stids = {reactome_id_to_uuid.get(u, "") for u in output_uuids} + if PI_STID in output_stids: + phosphatase_rxn_uuids.add(rxn_uuid) + + # Identify ubiquitin-ligase reactions TOPOLOGICALLY: those that take + # ubiquitin (Ub) as an INPUT. Reactome models ubiquitination by + # consuming free Ub and producing a ubiquitinated form of the target, + # so this is a clean structural signal (no display-name string matching + # required). For each such reaction we then identify the SUBSTRATE by + # gene-level matching between input-complex members and output members: + # the protein appearing in both (by referenceEntity gene) is the one + # being modified, and its free-form network nodes are the correct + # depletion targets — NOT the assembled complex that appears as the + # literal reaction input. + + # Collect all Reactome reaction ids in this pathway from catalyst_map + # (each catalyst row has a reaction_id and reaction_uuid). + rxn_uuid_to_rstid: Dict[str, str] = {} + if not catalyst_map.empty: + for _, row in catalyst_map.iterrows(): + ru = str(row["reaction_uuid"]); rs = str(row["reaction_id"]) + if ru and rs: + rxn_uuid_to_rstid[ru] = rs + unique_rstids = list({rs for rs in rxn_uuid_to_rstid.values() if rs}) + + # For each candidate reaction, ask neo4j: is Ub an input? Then collect + # input/output protein gene names (decomposing complexes/sets to leaves). + # Returns reaction_stid → (set of substrate genes, set of input-protein stids). + ubiquitin_subst_by_rstid: Dict[str, Tuple[Set[str], Set[str]]] = {} + if unique_rstids: + from src.neo4j_connector import get_graph + try: + # Reactions with any Ub stId as input + ub_rows = get_graph().run( + "UNWIND $ids AS id " + "MATCH (rle:ReactionLikeEvent {stId: id})-[:input]->(ub:PhysicalEntity) " + "WHERE ub.stId IN $ub_stids RETURN DISTINCT id AS rxn", + ids=unique_rstids, + ub_stids=list(_UBIQUITIN_STIDS), + ).data() + ubiq_rstids = [r["rxn"] for r in ub_rows] + # For each ubiquitination reaction, gather input-leaf proteins + # (decomposed through hasComponent/hasMember/hasCandidate) and + # their referenceEntity genes; do the same for outputs. + in_rows = get_graph().run( + "UNWIND $ids AS id " + "MATCH (rle:ReactionLikeEvent {stId: id})-[:input]->(inp:PhysicalEntity) " + "WHERE NOT inp.stId IN $ub_stids " + "OPTIONAL MATCH (inp)-[:hasComponent|hasMember|hasCandidate*0..3]->(leaf:PhysicalEntity) " + "OPTIONAL MATCH (leaf)-[:referenceEntity]->(re:ReferenceEntity) " + "RETURN id AS rxn, leaf.stId AS leaf_stid, re.geneName AS genes", + ids=ubiq_rstids, + ub_stids=list(_UBIQUITIN_STIDS), + ).data() if ubiq_rstids else [] + out_rows = get_graph().run( + "UNWIND $ids AS id " + "MATCH (rle:ReactionLikeEvent {stId: id})-[:output]->(o:PhysicalEntity) " + "WHERE NOT o.stId IN $ub_stids " + "OPTIONAL MATCH (o)-[:hasComponent|hasMember|hasCandidate*0..3]->(leaf:PhysicalEntity) " + "OPTIONAL MATCH (leaf)-[:referenceEntity]->(re:ReferenceEntity) " + "RETURN id AS rxn, re.geneName AS genes", + ids=ubiq_rstids, + ub_stids=list(_UBIQUITIN_STIDS), + ).data() if ubiq_rstids else [] + in_by_rxn: Dict[str, List[Tuple[Optional[str], Optional[List[str]]]]] = {} + for r in in_rows: + in_by_rxn.setdefault(r["rxn"], []).append((r["leaf_stid"], r["genes"])) + out_by_rxn: Dict[str, Set[str]] = {} + for r in out_rows: + g = r["genes"] + if g: + for gn in g: + out_by_rxn.setdefault(r["rxn"], set()).add(gn) + for rxn in ubiq_rstids: + output_genes = out_by_rxn.get(rxn, set()) + if not output_genes: continue + # The substrate: input-leaf proteins whose gene also appears + # in the output (modified form). Collect their leaf stids. + subst_stids: Set[str] = set() + subst_genes: Set[str] = set() + for leaf_stid, leaf_genes in in_by_rxn.get(rxn, []): + if not leaf_stid or not leaf_genes: continue + common = output_genes & set(leaf_genes) + if common: + subst_stids.add(leaf_stid) + subst_genes.update(common) + if subst_stids: + ubiquitin_subst_by_rstid[rxn] = (subst_genes, subst_stids) + except Exception as exc: + logger.warning(f"Ubiquitin topology lookup failed: {exc}") + + # Build network stid → list of UUIDs index from pathway_logic_network_data, + # so we can target the substrate's NETWORK NODES (not just the leaf + # protein stId, which may not be a direct node). + stid_to_uuids_in_net: Dict[str, List[str]] = {} + seen_uuids: Set[str] = set() + for edge in pathway_logic_network_data: + for uid in (edge.get("source_id"), edge.get("target_id")): + if not uid or uid in seen_uuids: continue + seen_uuids.add(uid) + sid = reactome_id_to_uuid.get(uid, "") + if sid: + stid_to_uuids_in_net.setdefault(sid, []).append(uid) + + # Emit depletion edges. For phosphatase reactions: catalyst → input + # (free substrate IS the input). For ubiquitin reactions: catalyst → + # all network UUIDs of the substrate protein stId (free form, not the + # complex that's the literal reaction input). + seen_edges: set = set() + n_emitted_phos = 0 + n_emitted_ub = 0 + # Phosphatase pass — existing behavior. + for rxn_uuid in phosphatase_rxn_uuids: + catalyst_uuids = by_target_catalysts.get(rxn_uuid, []) + inputs = by_target_inputs.get(rxn_uuid, []) + if not catalyst_uuids or not inputs: + continue + for cat_uuid in catalyst_uuids: + cat_stid = reactome_id_to_uuid.get(cat_uuid, "") + for inp_uuid in inputs: + if cat_uuid == inp_uuid: + continue + inp_stid = reactome_id_to_uuid.get(inp_uuid, "") + if inp_stid == cat_stid and inp_stid: + continue # same biological entity at different positions + if inp_stid in _COFACTOR_STIDS: + continue + key = (cat_uuid, inp_uuid) + if key in seen_edges: + continue + seen_edges.add(key) + pathway_logic_network_data.append({ + "source_id": cat_uuid, + "target_id": inp_uuid, + "pos_neg": "neg", + "and_or": "and", + "edge_type": "depletion", + "stoichiometry": 1.0, + }) + n_emitted_phos += 1 + + # Ubiquitin pass — emit catalyst → free-substrate-UUIDs depletion edges. + # For each VR uuid corresponding to a ubiquitin reaction, get its + # catalysts, then for each substrate stid, look up all network UUIDs of + # that substrate (free TP53 nodes, not the assembled complex) and emit. + for ru, rs in rxn_uuid_to_rstid.items(): + info = ubiquitin_subst_by_rstid.get(rs) + if info is None: + continue + _, subst_stids = info + catalyst_uuids = by_target_catalysts.get(ru, []) + if not catalyst_uuids: + continue + for cat_uuid in catalyst_uuids: + cat_stid = reactome_id_to_uuid.get(cat_uuid, "") + for subst_stid in subst_stids: + if subst_stid == cat_stid: continue + if subst_stid in _COFACTOR_STIDS: continue + target_uuids = stid_to_uuids_in_net.get(subst_stid, []) + for tgt_uuid in target_uuids: + if tgt_uuid == cat_uuid: continue + key = (cat_uuid, tgt_uuid) + if key in seen_edges: continue + seen_edges.add(key) + pathway_logic_network_data.append({ + "source_id": cat_uuid, + "target_id": tgt_uuid, + "pos_neg": "neg", + "and_or": "and", + "edge_type": "depletion", + "stoichiometry": 1.0, + }) + n_emitted_ub += 1 + logger.info( + f"Emitted {n_emitted_phos + n_emitted_ub} substrate-depletion edges " + f"({len(phosphatase_rxn_uuids)} phosphatase reactions, " + f"{len(ubiquitin_subst_by_rstid)} ubiquitin-ligase reactions with " + f"identified substrate; topology-based)" + ) + + +_handoff_leaf_cache: Dict[str, frozenset] = {} + + +def _node_leaves(node_id: str) -> frozenset: + """Non-cofactor terminal leaf stIds contained in a node id. + + variant node → the leaves encoded after ``::variant::``; simple complex → + its terminal components; anything else → itself. Cofactors and ubiquitin are + excluded so they can't act as spurious connectivity carriers. + """ + if node_id in _handoff_leaf_cache: + return _handoff_leaf_cache[node_id] + if "::variant::" in node_id: + s = {m for m in node_id.split("::variant::")[-1].split("_") if m.startswith("R-")} + else: + try: + from src.neo4j_connector import get_labels + s = set(get_terminal_components(node_id)) if "Complex" in get_labels(node_id) else {node_id} + except Exception: + s = {node_id} + leaves = frozenset(s - _COFACTOR_STIDS - _UBIQUITIN_STIDS) + _handoff_leaf_cache[node_id] = leaves + return leaves + + +def _emit_precedingevent_handoff_edges( + pathway_logic_network_data: List[Dict[str, Any]], + reaction_connections: pd.DataFrame, + reactome_to_vr: Dict[str, List[str]], + vr_entities: Dict[str, tuple], + entity_uuid_registry: Dict[tuple, str], +) -> None: + """Restore curator-intended connectivity dropped by complex bundling. + + A ``precedingEvent`` is the curator asserting that at least one entity flows + from the preceding reaction to the following one. The generator realizes that + only when the two reactions share a *whole* entity. But a molecule is often + handed off as a *component* — bound in a complex on one side, free/other- + complex on the other — so bundling makes them different nodes and the link is + lost. This is NOT a heuristic: the ``precedingEvent`` edge is in Neo4j. + + Rule (per Adam): for each precedingEvent pair, do nothing if the reactions are + ALREADY connected by a shared whole entity (the curator's asserted entity is + represented). Only when they share no whole entity — yet the curator says + they're connected — add ONE bridge, between the output/input nodes that share + the most non-cofactor components (the most likely real carrier). "Skip already- + connected pairs" + "one bridge per gap" keeps this bounded by the number of + precedingEvent gaps (hundreds), not the variant×variant blow-up. + """ + # Carrier specificity: a leaf that appears in many nodes is a promiscuous + # subunit (e.g. RBL2, a pocket protein in many complexes). Bridging on such a + # hub spreads a perturbation to readouts it doesn't affect (false positives). + # Count how many distinct nodes each leaf appears in; only leaves appearing + # in <= HUB_MAX nodes are allowed to act as the transferred carrier. + HUB_MAX = int(os.environ.get("LNG_HANDOFF_HUB_MAX", "3")) + leaf_nodes: Dict[str, set] = {} + all_node_ids: Set[str] = set() + for (ins_, outs_, _si, _so) in vr_entities.values(): + all_node_ids.update(ins_); all_node_ids.update(outs_) + for nid in all_node_ids: + for lf in _node_leaves(nid): + leaf_nodes.setdefault(lf, set()).add(nid) + specific = {lf for lf, ns in leaf_nodes.items() if len(ns) <= HUB_MAX} + + existing = {(e["source_id"], e["target_id"]) for e in pathway_logic_network_data} + seen: Set[tuple] = set() + n = 0 + for _, conn in reaction_connections.iterrows(): + pre = conn.get("preceding_reaction_id"); fol = conn.get("following_reaction_id") + if pd.isna(pre) or pd.isna(fol): + continue + # Gather (node_id, uuid) for the preceding reaction's outputs and the + # following reaction's inputs, across all their virtual reactions. + outs = [] + for p_vr in reactome_to_vr.get(pre, []): + for on in vr_entities.get(p_vr, ([], [], {}, {}))[1]: + u = entity_uuid_registry.get((on, p_vr, "output")) + if u: + outs.append((on, u)) + ins = [] + for f_vr in reactome_to_vr.get(fol, []): + for inn in vr_entities.get(f_vr, ([], [], {}, {}))[0]: + u = entity_uuid_registry.get((inn, f_vr, "input")) + if u: + ins.append((inn, u)) + if not outs or not ins: + continue + # Already connected? (whole-entity match -> Phase 2 gave a shared UUID) + if {u for _, u in outs} & {u for _, u in ins}: + continue + # Otherwise bridge the single best output/input node pair, scored by the + # number of shared SPECIFIC (non-hub) carrier components. A pair sharing + # only hub subunits scores 0 and is not bridged. + best = None; best_n = 0 + for on, ou in outs: + lon = _node_leaves(on) & specific + if not lon: + continue + for inn, iu in ins: + if ou == iu: + continue + sh = len(lon & _node_leaves(inn)) + if sh > best_n: + best_n = sh; best = (ou, iu) + if best and best_n > 0 and best[0] != best[1] and best not in existing and best not in seen: + seen.add(best) + pathway_logic_network_data.append({ + "source_id": best[0], + "target_id": best[1], + "pos_neg": "pos", + "and_or": "or", + "edge_type": "handoff", + "stoichiometry": 1, + "edge_reaction_id": None, + }) + n += 1 + logger.info( + f"Emitted {n} precedingEvent hand-off edges " + f"(one bridge per otherwise-disconnected precedingEvent gap)" + ) + + def _emit_boundary_decomposition_edges( pathway_logic_network_data: List[Dict[str, Any]], - root_input_eids: Set[str], - terminal_output_eids: Set[str], - root_input_uuid_cache: Dict[str, str], - terminal_output_uuid_cache: Dict[str, str], reactome_id_to_uuid: Dict[str, str], ) -> None: - """Append synthetic edges that expose leaves of root/terminal complexes. - - For each root-input complex C with components {A, B, ...}, emit - ``A → C``, ``B → C``, ... edges of edge_type='assembly'. For each - terminal-output complex, emit ``C → A``, ``C → B``, ... of - edge_type='dissociation'. Each leaf shares a single UUID across all - boundary contexts so that perturbing a leaf at the assembly side - propagates through any downstream dissociation that reads the same - species. - - Intermediate complexes (those produced by some reaction AND consumed - by another in this pathway) are intentionally NOT expanded — they're - real biological species flowing between reactions, and the AB dimer - is a different molecule from free A and free B. See - docs/DESIGN_DECISIONS.md, "Two layers of decomposition." - - Simple-leaf root/terminal entities (proteins, small molecules) are - skipped: they're already perturbable as themselves. - - A leaf reuses any UUID the entity already has elsewhere in the network - (regular VR inputs/outputs, regulators, catalysts) so that perturbing a - protein in one role propagates through every other role. Without this, - boundary leaves would be disconnected duplicate nodes for the same - biological entity. + """Expose the members of every root-input and terminal-output complex. + + Boundary membership is decided **positionally, per network occurrence** — + not by a global stId set difference. A node is a *root input* if it is a + source but never a target (no reaction produces it); a *terminal output* if + it is a target but never a source (no reaction consumes it). The same + complex stId can appear at several positions: the occurrences that are root + inputs get decomposed, the occurrence sitting intermediate between two + reactions is left intact — exactly as a real species should be. + + (The previous implementation used ``all_input_eids - all_output_eids`` over + the whole pathway, which removed a complex's stId entirely the moment it was + produced *anywhere*, leaving its genuine root-input occurrences undecomposed. + Curator perturbations target individual proteins, so those proteins must be + addressable wherever they enter or leave the pathway.) + + The two boundary directions produce deliberately OPPOSITE kinds of node: + + * **Assembly** (root input): for complex C with members {A, B, ...} emit + ``A → C``, ``B → C`` (``edge_type='assembly'``). Members are *shared* + upstream perturbation handles — a member reuses any UUID it already has in + the network (else one freshly-minted UUID shared across occurrences). So a + complex appearing as a root input at 15 positions gives member A one node + with 15 assembly edges; knocking out A then propagates into all of them + (the complex requires A — a real forward dependency). + + * **Dissociation** (terminal output): for complex C emit ``C → A``, ``C → B`` + (``edge_type='dissociation'``) where each member is a FRESH, SEPARATE + readout node — one per (terminal-complex occurrence, member), carrying the + member's stId, value inherited from the complex, and with NO outgoing + edges. These are downstream *sinks*, NOT shared with the member's + functional/assembly node. A terminal output is by definition consumed by + nothing, so its members carry no forward signal; sharing them with the + functional protein would (wrongly) inject the complex's activity into every + other reaction that protein touches. Keeping them separate lets you measure + how affected each member is *at that location* (read the sink; aggregate + across a member's sinks for an overall figure) without any cross-talk. """ from src.neo4j_connector import get_labels - # Build stId → existing UUID lookup from everything assigned so far - # (entity registry from VR phases, plus regulator/catalyst UUIDs added - # by append_regulators). reactome_id_to_uuid is keyed by UUID, so invert. + # Positional roots / terminals from the current edge list. + sources: Set[str] = set() + targets: Set[str] = set() + for edge in pathway_logic_network_data: + sources.add(edge["source_id"]) + targets.add(edge["target_id"]) + root_uuids = sources - targets # produced by no reaction in this pathway + terminal_uuids = targets - sources # consumed by no reaction in this pathway + + # stId → existing UUID, so a member reuses the node it already has elsewhere + # (free protein, regulator, catalyst) rather than becoming a disconnected dup. stid_to_existing_uuid: Dict[str, str] = {} for existing_uuid, stid in reactome_id_to_uuid.items(): if stid not in stid_to_existing_uuid: @@ -605,22 +1230,34 @@ def _leaf_uuid(leaf_stid: str) -> str: return leaf_uuid_registry[leaf_stid] def _is_complex(entity_id: str) -> bool: - return "Complex" in get_labels(entity_id) - + # Synthetic variant IDs (from negative-regulator variant decomposition) + # are not in Neo4j and shouldn't be further decomposed at the boundary. + # Tolerate any other unknown stIds too (e.g., entities added by other + # synthetic emissions) — if the lookup fails, assume it's not a complex + # and skip decomposition rather than crashing. + if "::variant::" in entity_id: + return False + try: + return "Complex" in get_labels(entity_id) + except IndexError: + return False + + seen_edges: Set[tuple] = set() assembly_count = 0 - for eid in root_input_eids: - if not _is_complex(eid): - continue - complex_uuid = root_input_uuid_cache.get(eid) - if not complex_uuid: + for complex_uuid in root_uuids: + stid = reactome_id_to_uuid.get(complex_uuid) or "" + if not stid or not _is_complex(stid): continue - leaves = get_terminal_components(eid) - # If the only "leaf" is the complex itself, there's nothing to expose. - if leaves == {str(eid)}: + leaves = get_terminal_components(stid) + if leaves == {str(stid)}: # nothing below the complex to expose continue for leaf in leaves: + leaf_uuid = _leaf_uuid(leaf) + if (leaf_uuid, complex_uuid) in seen_edges: + continue + seen_edges.add((leaf_uuid, complex_uuid)) pathway_logic_network_data.append({ - "source_id": _leaf_uuid(leaf), + "source_id": leaf_uuid, "target_id": complex_uuid, "pos_neg": "pos", "and_or": "and", @@ -630,19 +1267,21 @@ def _is_complex(entity_id: str) -> bool: assembly_count += 1 dissociation_count = 0 - for eid in terminal_output_eids: - if not _is_complex(eid): + for complex_uuid in terminal_uuids: + stid = reactome_id_to_uuid.get(complex_uuid) or "" + if not stid or not _is_complex(stid): continue - complex_uuid = terminal_output_uuid_cache.get(eid) - if not complex_uuid: - continue - leaves = get_terminal_components(eid) - if leaves == {str(eid)}: + leaves = get_terminal_components(stid) + if leaves == {str(stid)}: continue for leaf in leaves: + # Fresh per-occurrence readout sink — NOT _leaf_uuid (which would + # share the member's functional node and re-introduce cross-talk). + readout_uuid = str(uuid.uuid4()) + reactome_id_to_uuid[readout_uuid] = leaf pathway_logic_network_data.append({ "source_id": complex_uuid, - "target_id": _leaf_uuid(leaf), + "target_id": readout_uuid, "pos_neg": "pos", "and_or": "and", "edge_type": "dissociation", @@ -652,9 +1291,9 @@ def _is_complex(entity_id: str) -> bool: if assembly_count or dissociation_count: logger.info( - f"Boundary expansion: {assembly_count} assembly edges, " - f"{dissociation_count} dissociation edges, " - f"{len(leaf_uuid_registry)} unique boundary leaves" + f"Boundary expansion (positional): {assembly_count} assembly edges " + f"(shared member handles), {dissociation_count} dissociation edges " + f"(separate readout sinks), {len(leaf_uuid_registry)} new assembly leaves" ) @@ -696,10 +1335,26 @@ def append_regulators( ] for map_df, pos_neg, edge_type in regulator_configs: + # Negative regulators use VARIANT decomposition: an inhibitor complex + # with an internal EntitySet expands into one entity per cartesian + # variant of that EntitySet, but each variant is kept as a single + # complex — NOT broken down into individual subunits. Without this, + # HSP90 / CDC37 / ERBIN of an ERBB2 inhibitor complex would each + # become standalone inhibitor edges, and any unrelated reaction + # producing those bystander proteins would spuriously crush + # downstream signal. + # + # Catalysts and positive regulators keep SUBUNIT decomposition: each + # holoenzyme subunit is biologically AND-required for catalysis, so + # decomposing to terminal proteins is correct there. + variant_decomposition = (pos_neg == "neg") + for _, row in map_df.iterrows(): entity_id = str(row["entity_id"]) - terminal_members = _decompose_regulator_entity(entity_id) + terminal_members = _decompose_regulator_entity( + entity_id, variant_decomposition=variant_decomposition + ) # and_or expresses reaction-level requirement, not within-entity # decomposition logic. Anything that contributes to a reaction @@ -845,6 +1500,7 @@ def create_pathway_logic_network( "and_or": pd.Series(dtype="str"), "edge_type": pd.Series(dtype="str"), "stoichiometry": pd.Series(dtype="Int64"), + "edge_reaction_id": pd.Series(dtype="str"), } pathway_logic_network_data: List[Dict[str, Any]] = [] @@ -943,9 +1599,15 @@ def create_pathway_logic_network( # Output edges get "or" when the entity is produced by multiple VRs. entity_producer_count = _build_entity_producer_count(vr_entities) + # vr_uid -> Reactome reaction stId, for edge provenance (edge_reaction_id). + vr_to_reaction: Dict[str, str] = dict( + zip(reaction_id_map["uid"].astype(str), reaction_id_map["reactome_id"].astype(str)) + ) + for vr_uid, (input_ids, output_ids, input_stoich, output_stoich) in vr_entities.items(): if not input_ids or not output_ids: continue + reaction_stid = vr_to_reaction.get(str(vr_uid)) for eid in input_ids: input_uuid = entity_uuid_registry[(eid, vr_uid, "input")] @@ -956,6 +1618,7 @@ def create_pathway_logic_network( "and_or": "and", "edge_type": "input", "stoichiometry": input_stoich.get(eid, 1), + "edge_reaction_id": reaction_stid, }) for eid in output_ids: @@ -973,6 +1636,7 @@ def create_pathway_logic_network( "and_or": and_or, "edge_type": "output", "stoichiometry": output_stoich.get(eid, 1), + "edge_reaction_id": reaction_stid, }) # Log UUID registry statistics @@ -1008,23 +1672,60 @@ def create_pathway_logic_network( entity_uuid_registry=entity_uuid_registry, ) - # Boundary expansion: root-input and terminal-output complexes get - # synthetic assembly / dissociation edges to their leaf components so - # individual proteins are perturbable / readable at the network - # boundary. Intermediate complexes are deliberately left intact — - # they're the actual biological species flowing between reactions. - # See docs/DESIGN_DECISIONS.md, "Two layers of decomposition." + # Substrate-depletion edges: for each catalytic reaction, emit edges + # `catalyst → input` with edge_type="depletion". These capture the + # biology that a catalyst REMOVES its substrate (PTEN dephosphorylates + # PIP3, MDM2 ubiquitinates TP53, etc.). The deltasignal solver applies + # divide-form inhibition to these edges specifically, so catalyst-knockout + # boosts the substrate via de-repression. Skips small-molecule cofactor + # inputs (ATP/H2O/Pi/etc.) to avoid noise. + _emit_substrate_depletion_edges( + pathway_logic_network_data=pathway_logic_network_data, + reactome_id_to_uuid=reactome_id_to_uuid, + catalyst_map=catalyst_map, + ) + + # Boundary expansion: every root-input and terminal-output complex + # occurrence gets synthetic assembly / dissociation edges to its member + # proteins, so individual subunits are perturbable / readable wherever + # they enter or leave the pathway. Decided positionally per occurrence + # (a complex that is also intermediate elsewhere keeps that intermediate + # node intact). See docs/DESIGN_DECISIONS.md, "Two layers of decomposition." _emit_boundary_decomposition_edges( pathway_logic_network_data=pathway_logic_network_data, - root_input_eids=root_input_eids, - terminal_output_eids=terminal_output_eids, - root_input_uuid_cache=root_input_uuid_cache, - terminal_output_uuid_cache=terminal_output_uuid_cache, reactome_id_to_uuid=reactome_id_to_uuid, ) + # Restore curator-intended connectivity that complex-bundling drops: two + # precedingEvent-linked reactions that hand off a shared COMPONENT (bound in + # a complex on one side, free/other-complex on the other) share no whole + # entity, so they were left disconnected. Honoring the curated precedingEvent + # by bridging on the shared component was tried three ways (naive / max-shared + # / hub-guarded) and was net-NEGATIVE on the benchmark every time: the bridge + # injects roughly as much spurious coupling as real signal it recovers (same + # reason the member-exploded network tied set-variant at ~69%). Kept for + # future work but OFF by default. See memory project_complex_as_node_result. + if os.environ.get("LNG_HANDOFF_EDGES", "0") != "0": + _emit_precedingevent_handoff_edges( + pathway_logic_network_data=pathway_logic_network_data, + reaction_connections=reaction_connections, + reactome_to_vr=reactome_to_vr, + vr_entities=vr_entities, + entity_uuid_registry=entity_uuid_registry, + ) + # Create final DataFrame pathway_logic_network = pd.DataFrame(pathway_logic_network_data, columns=list(columns.keys())) + # Coerce stoichiometry to nullable Int64 — emission sites use a mix of + # int (`1`) and float (`1.0`) literals, which makes pandas infer float64 + # for the column and serialize as `1.0` in the CSV. Force integer so the + # column reads as a clean whole-number count (Reactome's stoichiometries + # are all integers for the pathways we care about; if a fractional value + # ever appears it will surface as a parse error rather than silent loss). + if not pathway_logic_network.empty: + pathway_logic_network["stoichiometry"] = ( + pathway_logic_network["stoichiometry"].astype("Int64") + ) # Find root inputs and terminal outputs root_inputs = find_root_inputs(pathway_logic_network) @@ -1046,7 +1747,8 @@ def create_pathway_logic_network( logic_network=pathway_logic_network, uuid_mapping=reactome_id_to_uuid, catalyst_regulator_map=catalyst_regulator_uuid_map, - reaction_id_map=reaction_id_map + reaction_id_map=reaction_id_map, + entity_uuid_registry=entity_uuid_registry, ) def find_root_inputs(pathway_logic_network: pd.DataFrame) -> List[Any]: @@ -1131,3 +1833,282 @@ def export_uuid_to_reactome_mapping( mapping_df.to_csv(output_file, index=False) logger.info(f"Exported UUID to Reactome stable ID mapping with {len(mapping_df)} entries") + + +def export_entity_reaction_proxy_mapping( + pathway_logic_network: pd.DataFrame, + reaction_id_map: pd.DataFrame, + reactome_id_to_uuid: Dict[str, str], + pathway_id: str, + output_file: str, +) -> None: + """Map curated species absent from the UUID mapping to a reaction-flux proxy. + + Complexes/EntitySets that contain an EntitySet are expanded into virtual + variants when the logic network is built (see docs/DESIGN_DECISIONS.md, + "EntitySet expansion produces multiple virtual reactions"). The variants get + their own UUIDs but the *parent's* stId is not preserved anywhere in + ``stid_to_uuid_mapping.csv`` — so a consumer that knows a curated species by + its Reactome stId (e.g. MP-BioPath key-outputs, which are predominantly + Complexes) can't find it, even though the reaction that produces it *is* in + the network. + + The right proxy for "is species S present?" is the flux through the reaction + that produces it: if S is the output of reaction R, then R's node activity is + a direct, tight readout of S's production. Mapping S to its *terminal + components* instead would be far too lax — a hub protein like β-catenin + appears in dozens of reaction contexts, so a max over its component UUIDs + reads "active" almost everywhere and discriminates nothing. + + For each PhysicalEntity that participates in the pathway but is not directly + present in the UUID mapping, we therefore record the UUIDs of the reactions + that **output** it (the producing reactions). If a species has no producing + reaction in the network we fall back to reactions that **consume** it as an + input — its presence is still implied by that reaction's activity. + + The primary ``stid_to_uuid_mapping.csv`` is intentionally left untouched: it + keeps its one-row-per-UUID identity contract, and this supplementary file + carries the (many-to-many) species → proxy-reaction relationship. + + Output CSV columns: + - entity_stable_id: a species stId absent from the UUID mapping + - proxy_uuid: a reaction UUID present in the network whose flux proxies it + - proxy_role: 'producing' (entity is the reaction's output) or + 'consuming' (entity is the reaction's input) + """ + from src.neo4j_connector import ( + get_pathway_participating_entities, + get_pathway_entity_reactions, + ) + + network_uuids: Set[str] = set() + network_uuids.update(pathway_logic_network['source_id'].dropna().unique()) + network_uuids.update(pathway_logic_network['target_id'].dropna().unique()) + + # stable_id of every entity directly addressable in the mapping. + present_stids: Set[str] = set() + if reactome_id_to_uuid: + sample_key = next(iter(reactome_id_to_uuid.keys())) + uuid_keyed = '-' in str(sample_key) + for k, v in reactome_id_to_uuid.items(): + entity_uuid, stid = (k, v) if uuid_keyed else (v, k) + if entity_uuid in network_uuids: + present_stids.add(str(stid)) + + # reaction stId -> [reaction UUIDs present in the network] + reaction_stid_to_uuids: Dict[str, List[str]] = {} + for _, row in reaction_id_map.iterrows(): + ruuid = str(row['uid']) + if ruuid in network_uuids: + reaction_stid_to_uuids.setdefault(str(row['reactome_id']), []).append(ruuid) + + participating = get_pathway_participating_entities(pathway_id) + missing = {e for e in participating if e not in present_stids} + if not missing: + pd.DataFrame(columns=['entity_stable_id', 'proxy_uuid', 'proxy_role']).to_csv( + output_file, index=False) + logger.info("Entity-reaction proxy mapping: no missing species to proxy") + return + + # entity stId -> {'output': [reaction stIds], 'input': [reaction stIds]} + entity_reactions = get_pathway_entity_reactions(pathway_id, list(missing)) + + rows: List[Dict[str, str]] = [] + for entity_stid in missing: + roles = entity_reactions.get(entity_stid, {}) + # Prefer producing reactions; fall back to consuming if none are present. + for role_name, rel_key in (('producing', 'output'), ('consuming', 'input')): + proxy_uuids: List[str] = [] + for rxn_stid in roles.get(rel_key, []): + proxy_uuids.extend(reaction_stid_to_uuids.get(str(rxn_stid), [])) + if proxy_uuids: + for puuid in dict.fromkeys(proxy_uuids): # de-dup, keep order + rows.append({'entity_stable_id': str(entity_stid), + 'proxy_uuid': puuid, + 'proxy_role': role_name}) + break # don't also emit consuming rows once producing matched + + out_df = pd.DataFrame(rows, columns=['entity_stable_id', 'proxy_uuid', 'proxy_role']) + if not out_df.empty: + out_df = out_df.sort_values(['entity_stable_id', 'proxy_uuid']) + out_df.to_csv(output_file, index=False) + n_entities = out_df['entity_stable_id'].nunique() if not out_df.empty else 0 + logger.info( + f"Exported entity-reaction proxy mapping: {len(out_df)} rows " + f"covering {n_entities} of {len(missing)} missing species" + ) + + +# --------------------------------------------------------------------------- +# Schema-backed provenance exports (see schema/logic_network.linkml.yaml) +# --------------------------------------------------------------------------- +def _uuid_to_stable_id_map(pathway_logic_network: pd.DataFrame, + uuid_mapping: Dict[str, str]) -> Dict[str, str]: + """uuid -> node string id (stId or ``{parent}::variant::{members}``). + + ``uuid_mapping`` (reactome_id_to_uuid) can be stored either direction; detect + it the same way :func:`export_uuid_to_reactome_mapping` does. + """ + all_uuids: Set[str] = set() + all_uuids.update(pathway_logic_network["source_id"].dropna().astype(str).unique()) + all_uuids.update(pathway_logic_network["target_id"].dropna().astype(str).unique()) + out: Dict[str, str] = {} + if uuid_mapping: + sample = next(iter(uuid_mapping.keys())) + uuid_keyed = isinstance(sample, str) and sample.count("-") >= 4 and "::" not in sample + if uuid_keyed: + for u, s in uuid_mapping.items(): + if str(u) in all_uuids: + out[str(u)] = str(s) + else: + for s, u in uuid_mapping.items(): + if str(u) in all_uuids: + out[str(u)] = str(s) + return out + + +_sets_chosen_cache: Dict[str, tuple] = {} + + +def _derive_sets_and_chosen(parent_complex: str, member_leaves: Set[str]) -> tuple: + """(source_set_ids, chosen_member_ids) for a set_variant, from Neo4j.""" + if parent_complex in _sets_chosen_cache: + sets, chooser = _sets_chosen_cache[parent_complex] + else: + from src.neo4j_connector import get_labels, get_complex_components, get_set_members + sets = [] + chooser = {} + try: + comps = get_complex_components(parent_complex) + except Exception: + comps = {} + for m in comps: + try: + labels = get_labels(m) + except Exception: + continue + if any(lbl in labels for lbl in ("EntitySet", "DefinedSet", "CandidateSet")): + sets.append(str(m)) + for sm in get_set_members(m): + chooser.setdefault(str(m), []).append( + (str(sm), frozenset(get_terminal_components(sm))) + ) + _sets_chosen_cache[parent_complex] = (sets, chooser) + chosen: List[str] = [] + for _set_id, options in chooser.items(): + for sm_id, sm_leaves in options: + if sm_leaves & member_leaves: + chosen.append(sm_id) + return sets, chosen + + +def export_nodes(pathway_logic_network: pd.DataFrame, + reaction_id_map: pd.DataFrame, + uuid_mapping: Dict[str, str], + output_file: str) -> None: + """Write nodes.csv — one row per node (see schema class ``Node``). + + Provenance is derived post-hoc from the node id string, the edge topology, + and Neo4j: node_kind, diagram_entity_id (the stId a diagram renders — parent + complex for a variant), member_leaves, and the set decomposition. + """ + from src.neo4j_connector import get_labels + uuid_to_str = _uuid_to_stable_id_map(pathway_logic_network, uuid_mapping) + vr_uids = set(reaction_id_map["uid"].astype(str)) + vr_to_reaction = dict(zip(reaction_id_map["uid"].astype(str), + reaction_id_map["reactome_id"].astype(str))) + + incoming_types: Dict[str, Set[str]] = {} + has_outgoing: Set[str] = set() + for _, e in pathway_logic_network.iterrows(): + s, t = e.get("source_id"), e.get("target_id") + if pd.notna(s): + has_outgoing.add(str(s)) + if pd.notna(t): + incoming_types.setdefault(str(t), set()).add(str(e.get("edge_type"))) + + all_uuids = set(uuid_to_str) | (vr_uids & (has_outgoing | set(incoming_types))) + rows: List[Dict[str, Any]] = [] + for u in sorted(all_uuids): + kind = "other"; diagram = None + members: List[str] = []; sets: List[str] = []; chosen: List[str] = [] + if u in vr_uids and u not in uuid_to_str: + kind, diagram = "reaction", vr_to_reaction.get(u) + else: + s = uuid_to_str.get(u) + if s is None: + pass + elif "::variant::" in s: + kind = "set_variant" + diagram = s.split("::variant::")[0] + members = [m for m in s.split("::variant::")[-1].split("_") + if m.startswith("R-")] + sets, chosen = _derive_sets_and_chosen(diagram, set(members)) + else: + diagram = s + inc = incoming_types.get(u, set()) + if "dissociation" in inc and u not in has_outgoing: + kind, members = "dissociation_sink", [s] + else: + try: + labels = get_labels(s) + except Exception: + labels = [] + if "Complex" in labels: + kind = "simple_complex" + try: + members = sorted(get_terminal_components(s)) + except Exception: + members = [s] + else: + kind, members = "simple_entity", [s] + rows.append({ + "uuid": u, + "node_kind": kind, + "diagram_entity_id": diagram, + "compartment": None, + "member_leaves": "|".join(members), + "source_sets": "|".join(sets), + "chosen_members": "|".join(chosen), + }) + cols = ["uuid", "node_kind", "diagram_entity_id", "compartment", + "member_leaves", "source_sets", "chosen_members"] + pd.DataFrame(rows, columns=cols).to_csv(output_file, index=False) + logger.info(f"Exported {len(rows)} nodes with provenance: {output_file}") + + +def export_node_reaction_context(entity_uuid_registry: Dict[tuple, str], + reaction_id_map: pd.DataFrame, + catalyst_regulator_map: pd.DataFrame, + output_file: str) -> None: + """Write node_reaction_context.csv — (node, reaction, role) location rows.""" + vr_to_reaction = dict(zip(reaction_id_map["uid"].astype(str), + reaction_id_map["reactome_id"].astype(str))) + seen: Set[tuple] = set() + rows: List[Dict[str, Any]] = [] + + for (eid, vr_uid, role), node_uuid in (entity_uuid_registry or {}).items(): + rid = vr_to_reaction.get(str(vr_uid)) + if rid is None or role not in ("input", "output"): + continue + key = (str(node_uuid), rid, role) + if key in seen: + continue + seen.add(key) + rows.append({"context_node": str(node_uuid), "reaction_id": rid, "role": role}) + + if catalyst_regulator_map is not None and not catalyst_regulator_map.empty: + for _, r in catalyst_regulator_map.iterrows(): + cr_uuid = r.get("uuid"); rid = r.get("reaction_id"); et = str(r.get("edge_type")) + if pd.isna(cr_uuid) or pd.isna(rid): + continue + role = "catalyst" if et == "catalyst" else "regulator" + key = (str(cr_uuid), str(rid), role) + if key in seen: + continue + seen.add(key) + rows.append({"context_node": str(cr_uuid), "reaction_id": str(rid), "role": role}) + + cols = ["context_node", "reaction_id", "role"] + pd.DataFrame(rows, columns=cols).to_csv(output_file, index=False) + logger.info(f"Exported {len(rows)} node-reaction-context rows: {output_file}") diff --git a/src/neo4j_connector.py b/src/neo4j_connector.py index 6cc7f96..a1707da 100755 --- a/src/neo4j_connector.py +++ b/src/neo4j_connector.py @@ -331,6 +331,102 @@ def get_top_level_pathways() -> List[Dict[str, Any]]: ) from e +def get_pathway_participating_entities(pathway_id: str) -> Set[str]: + """Return every PhysicalEntity stId that participates in the pathway's reactions. + + Walks all ReactionLikeEvents under the pathway (via hasEvent) and collects + the stIds of their inputs, outputs, catalysts, and regulators. This includes + the *intact* Complex/EntitySet entities as Reactome curates them — which is + exactly the set that may get decomposed into virtual variants in the logic + network and therefore lose their original stId from the UUID mapping. + + Args: + pathway_id: Reactome pathway stable ID (e.g., "R-HSA-69620") + + Returns: + Set of PhysicalEntity stable IDs. + + Raises: + ConnectionError: If Neo4j database is not accessible + """ + # Explicit, indexed patterns per role. A blanket variable-length match + # (e.g. ``-[*1..3]-``) over these relationship types is orders of magnitude + # slower and can time out on large pathways. + query: str = """ + MATCH (pathway:Pathway {stId: $pathway_id})-[:hasEvent*]->(r:ReactionLikeEvent) + OPTIONAL MATCH (r)-[:input|output]->(io:PhysicalEntity) + OPTIONAL MATCH (r)-[:catalystActivity]->(:CatalystActivity) + -[:physicalEntity]->(cat:PhysicalEntity) + OPTIONAL MATCH (r)-[:regulatedBy]->(:Regulation) + -[:regulator]->(reg:PhysicalEntity) + RETURN COLLECT(DISTINCT io.stId) + + COLLECT(DISTINCT cat.stId) + + COLLECT(DISTINCT reg.stId) AS stids + """ + try: + result = get_graph().run(query, pathway_id=pathway_id).data() + if not result: + return set() + return {s for s in result[0]["stids"] if s} + except Exception as e: + logger.error( + f"Error in get_pathway_participating_entities for {pathway_id}", + exc_info=True, + ) + raise ConnectionError( + f"Failed to query participating entities from Neo4j at " + f"{os.getenv('NEO4J_URL', 'bolt://localhost:7687')}. " + f"Original error: {str(e)}" + ) from e + + +def get_pathway_entity_reactions( + pathway_id: str, entity_ids: List[str] +) -> Dict[str, Dict[str, List[str]]]: + """For each entity, the pathway reactions that output (produce) or input it. + + Args: + pathway_id: Reactome pathway stable ID (e.g., "R-HSA-195721") + entity_ids: PhysicalEntity stIds to look up. + + Returns: + ``{entity_stId: {"output": [reaction_stId, ...], + "input": [reaction_stId, ...]}}`` + Entities with no participating reaction in the pathway are absent. + + Raises: + ConnectionError: If Neo4j database is not accessible + """ + if not entity_ids: + return {} + query: str = """ + MATCH (pathway:Pathway {stId: $pathway_id})-[:hasEvent*]->(r:ReactionLikeEvent) + MATCH (r)-[rel:input|output]->(e:PhysicalEntity) + WHERE e.stId IN $entity_ids + RETURN e.stId AS entity, type(rel) AS rel, + COLLECT(DISTINCT r.stId) AS reactions + """ + try: + result = get_graph().run( + query, pathway_id=pathway_id, entity_ids=list(entity_ids) + ).data() + out: Dict[str, Dict[str, List[str]]] = {} + for row in result: + out.setdefault(row["entity"], {})[row["rel"]] = [ + rid for rid in row["reactions"] if rid + ] + return out + except Exception as e: + logger.error( + f"Error in get_pathway_entity_reactions for {pathway_id}", exc_info=True + ) + raise ConnectionError( + f"Failed to query entity reactions from Neo4j at " + f"{os.getenv('NEO4J_URL', 'bolt://localhost:7687')}. " + f"Original error: {str(e)}" + ) from e + + def get_pathway_name(pathway_id: str) -> str: """Get the display name for a pathway by its stable ID. diff --git a/src/pathway_generator.py b/src/pathway_generator.py index 6141b93..adcd1bd 100755 --- a/src/pathway_generator.py +++ b/src/pathway_generator.py @@ -8,6 +8,9 @@ from src.decomposed_uid_mapping import decomposed_uid_mapping_column_types from src.logic_network_generator import ( create_pathway_logic_network, + export_entity_reaction_proxy_mapping, + export_node_reaction_context, + export_nodes, export_uuid_to_reactome_mapping, ) from src.neo4j_connector import get_reaction_connections @@ -119,10 +122,17 @@ def generate_pathway_file( logger.warning(f"Could not cache decomposition results: {e}") # Continue without caching + # Augment connectivity with curator-drawn diagram flow (product->substrate + # pairs the diagram links but precedingEvent may omit — esp. old pathways). + # Only the connectivity/merge step sees this; the matching layer above + # stays on pure precedingEvent. See reactome/logic-network-generator#39. + from src.diagram_connectivity import augment_reaction_connections + connectivity = augment_reaction_connections(pathway_id, reaction_connections) + # Generate logic network logger.info("Creating pathway logic network...") result = create_pathway_logic_network( - decomposed_uid_mapping, reaction_connections, best_matches + decomposed_uid_mapping, connectivity, best_matches ) # Save logic network (main output file users need) @@ -150,6 +160,45 @@ def generate_pathway_file( logger.error(f"Failed to write stable ID to UUID mapping file {uuid_to_reactome_file}: {e}") # Don't raise - this is supplementary + # Export entity→reaction proxy mapping. Curated species (often Complexes) + # that were expanded into virtual variants lose their own stId from the + # UUID mapping; this file points each such species at the UUIDs of the + # reactions that produce (or, failing that, consume) it, so consumers can + # read reaction flux as a proxy for the species' state. + proxy_mapping_file = pathway_output_dir / "entity_reaction_proxy_mapping.csv" + try: + export_entity_reaction_proxy_mapping( + result.logic_network, + result.reaction_id_map, + result.uuid_mapping, + pathway_id, + str(proxy_mapping_file), + ) + logger.info(f"Successfully exported entity-reaction proxy mapping: {proxy_mapping_file}") + except Exception as e: + logger.error(f"Failed to write entity-reaction proxy mapping file {proxy_mapping_file}: {e}") + # Don't raise - this is supplementary + + # Schema-backed provenance files (schema/logic_network.linkml.yaml): + # nodes.csv (node_kind, diagram_entity_id, member_leaves, set provenance) + # and node_reaction_context.csv (node<->reaction location layer). + try: + export_nodes( + result.logic_network, + result.reaction_id_map, + result.uuid_mapping, + str(pathway_output_dir / "nodes.csv"), + ) + export_node_reaction_context( + result.entity_uuid_registry, + result.reaction_id_map, + result.catalyst_regulator_map, + str(pathway_output_dir / "node_reaction_context.csv"), + ) + except Exception as e: + logger.error(f"Failed to write node provenance files: {e}", exc_info=True) + # Don't raise - supplementary + logger.info(f"Output directory: {pathway_output_dir}") except (ConnectionError, ValueError) as e: diff --git a/tests/test_autophagy_validation.py b/tests/test_autophagy_validation.py index 43e1b9c..f663369 100644 --- a/tests/test_autophagy_validation.py +++ b/tests/test_autophagy_validation.py @@ -400,7 +400,8 @@ class TestAutophagyEdgeProperties: def test_valid_edge_types(self, logic_network_sample): """All edge types should be valid.""" - valid = {'input', 'output', 'catalyst', 'regulator'} + valid = {'input', 'output', 'catalyst', 'regulator', + 'depletion', 'assembly', 'dissociation', 'handoff'} edge_types = set(logic_network_sample['edge_type'].unique()) invalid = edge_types - valid assert len(invalid) == 0, f"Invalid edge types: {invalid}" diff --git a/tests/test_diagram_connectivity.py b/tests/test_diagram_connectivity.py new file mode 100644 index 0000000..8580b92 --- /dev/null +++ b/tests/test_diagram_connectivity.py @@ -0,0 +1,77 @@ +"""Unit tests for diagram-sourced reaction connectivity (no Neo4j required). + +Builds a tiny synthetic diagram (two reactions sharing one entity glyph) and +checks that the shared-glyph product->substrate pair is extracted and unioned +into reaction_connections. See reactome/logic-network-generator#39. +""" +import json + +import pandas as pd +import pytest + +import src.diagram_connectivity as dc + + +def _write_diagram(dirpath, stid): + """Two reactions: A (R-HSA-100) outputs glyph 1; B (R-HSA-200) inputs glyph 1.""" + layout = { + "nodes": [{"id": 1, "reactomeId": 500, "displayName": "X"}], + "edges": [ + {"id": 10, "reactomeId": 100, "inputs": [], "outputs": [{"id": 1}], "catalysts": []}, + {"id": 20, "reactomeId": 200, "inputs": [{"id": 1}], "outputs": [], "catalysts": []}, + ], + } + graph = {"edges": [{"dbId": 100, "stId": "R-HSA-100"}, + {"dbId": 200, "stId": "R-HSA-200"}]} + (dirpath / f"{stid}.json").write_text(json.dumps(layout)) + (dirpath / f"{stid}.graph.json").write_text(json.dumps(graph)) + + +@pytest.fixture +def diagram_dir(tmp_path, monkeypatch): + monkeypatch.setenv("LNG_DIAGRAM_DIR", str(tmp_path)) + # isolate this pathway to its two reactions (avoid Neo4j) + monkeypatch.setattr(dc, "_pathway_reaction_stids", + lambda pid: {"R-HSA-100", "R-HSA-200"}) + return tmp_path + + +def test_shared_glyph_pair_extracted(diagram_dir): + _write_diagram(diagram_dir, "R-HSA-TEST") + pairs = dc.diagram_shared_product_pairs("R-HSA-TEST") + assert pairs == {("R-HSA-100", "R-HSA-200")} + + +def test_no_diagram_returns_empty(diagram_dir, monkeypatch): + # no diagram file written; ancestor lookup also finds nothing + monkeypatch.setattr(dc, "_covering_diagram_stid", lambda pid: "") + assert dc.diagram_shared_product_pairs("R-HSA-MISSING") == set() + + +def test_augment_adds_missing_pair(diagram_dir): + _write_diagram(diagram_dir, "R-HSA-TEST") + rc = pd.DataFrame({"preceding_reaction_id": ["R-HSA-100"], + "following_reaction_id": ["R-HSA-999"], + "event_status": ["Has Preceding Event"]}) + out = dc.augment_reaction_connections("R-HSA-TEST", rc) + added = out[out["event_status"] == "Diagram Shared Product"] + assert list(zip(added["preceding_reaction_id"], added["following_reaction_id"])) \ + == [("R-HSA-100", "R-HSA-200")] + + +def test_augment_skips_when_already_present(diagram_dir): + _write_diagram(diagram_dir, "R-HSA-TEST") + rc = pd.DataFrame({"preceding_reaction_id": ["R-HSA-100"], + "following_reaction_id": ["R-HSA-200"], + "event_status": ["Has Preceding Event"]}) + out = dc.augment_reaction_connections("R-HSA-TEST", rc) + assert len(out) == 1 # nothing added; pair already linked + + +def test_augment_disabled_by_env(diagram_dir, monkeypatch): + _write_diagram(diagram_dir, "R-HSA-TEST") + monkeypatch.setenv("LNG_DIAGRAM_CONNECTIVITY", "0") + rc = pd.DataFrame({"preceding_reaction_id": ["R-HSA-1"], + "following_reaction_id": ["R-HSA-2"], + "event_status": ["x"]}) + assert dc.augment_reaction_connections("R-HSA-TEST", rc) is rc diff --git a/tests/test_logic_network_generator.py b/tests/test_logic_network_generator.py index 1fb9eac..8cc151e 100644 --- a/tests/test_logic_network_generator.py +++ b/tests/test_logic_network_generator.py @@ -21,6 +21,7 @@ _register_entity_uuid, _get_or_create_entity_uuid, _resolve_vr_entities, + export_entity_reaction_proxy_mapping, ) @@ -243,23 +244,35 @@ def test_multi_source_convergence(self): assert uuid_from_vr1 == uuid_at_vr2 assert uuid_from_vr3 == uuid_at_vr2 - def test_no_duplicate_edges(self): - """Duplicate terminal IDs from decomposition should not produce duplicate edges. + def test_no_duplicate_edges(self, monkeypatch): + """_resolve_vr_entities must emit each node once (Set-deduped). - When multiple decomposition paths converge on the same terminal Reactome ID, - _resolve_to_terminal_reactome_ids returns duplicates. _resolve_vr_entities - must deduplicate them so Phase 3 doesn't create duplicate edges. + Since set-variant emission, node identities come from the reaction's + annotated input/output entities (mapped to nodes), and duplicates are + collapsed via a set. We mock the two Neo4j calls the resolver makes: + the reaction's annotated entities and their labels (simple proteins). """ - # Build a uid_index where hash "vr1-input" resolves to terminal ID "9933417" - # via two different nested paths, producing duplicates without dedup. - # uid_index maps hash -> (nested_uids, terminal_ids, stoich_map) + import src.logic_network_generator as m + from src import neo4j_connector + + # Reaction "1" annotates one simple-protein input and one output. + monkeypatch.setattr( + neo4j_connector, "get_reaction_input_output_ids", + lambda rid, io: {"9933417"} if io == "input" else {"12345"}, + ) + # Both entities are simple (not complexes/sets) -> mapped to themselves. + monkeypatch.setattr( + neo4j_connector, "get_labels", + lambda e: ["EntityWithAccessionedSequence"], + ) + + # input_hash resolves to member "9933417" via two convergent paths. uid_index = { - "vr1-input": (["nested-1", "nested-2"], set(), {}), # two nested paths, no direct terminals - "nested-1": ([], {"9933417"}, {"9933417": 1}), # both nested paths resolve to same terminal + "vr1-input": (["nested-1", "nested-2"], set(), {}), + "nested-1": ([], {"9933417"}, {"9933417": 1}), "nested-2": ([], {"9933417"}, {"9933417": 1}), "vr1-output": ([], {"12345"}, {"12345": 1}), } - reaction_id_map = pd.DataFrame({ "uid": ["vr1"], "input_hash": ["vr1-input"], @@ -268,17 +281,10 @@ def test_no_duplicate_edges(self): }) vr_entities = _resolve_vr_entities(reaction_id_map, uid_index) + input_ids, output_ids, _in_stoich, _out_stoich = vr_entities["vr1"] - input_ids, output_ids, input_stoich, output_stoich = vr_entities["vr1"] - - # _resolve_to_terminal_reactome_ids now returns dict (deduped by key), - # but stoichiometry accumulates: 1 + 1 = 2 from two nested paths - assert len(input_ids) == 1, ( - f"Expected 1 unique input ID, got {len(input_ids)}: {input_ids}" - ) - assert input_ids[0] == "9933417" - assert input_stoich["9933417"] == 2 # stoichiometry adds: 1 from nested-1 + 1 from nested-2 - assert len(output_ids) == 1 + assert input_ids == ["9933417"], f"expected one deduped input, got {input_ids}" + assert output_ids == ["12345"], f"expected one output, got {output_ids}" def test_root_input_same_entity_gets_one_uuid(self): """Root input entity appearing at multiple reactions should share one UUID.""" @@ -346,11 +352,18 @@ class TestBoundaryLeavesReuseExistingUUIDs: boundary expansion exists to enable. """ + def _root_edge(self, complex_uuid): + # Seed an edge that makes complex_uuid a root input: a source that is + # never a target (its target is an unmapped reaction node). + return [{"source_id": complex_uuid, "target_id": "u-reaction", + "pos_neg": "pos", "and_or": "and", + "edge_type": "input", "stoichiometry": 1}] + def test_leaf_reuses_uuid_when_entity_already_in_registry(self): """If MDM2 already has UUID U_existing in reactome_id_to_uuid (e.g. because it's a regular VR input or a regulator elsewhere), the - boundary expansion of MDM2:TP53 must use U_existing for the MDM2 - leaf — not a fresh one. + boundary expansion of root-input MDM2:TP53 must use U_existing for + the MDM2 leaf — not a fresh one. """ existing_mdm2_uuid = "u-existing-mdm2" complex_uuid = "u-complex" @@ -358,7 +371,7 @@ def test_leaf_reuses_uuid_when_entity_already_in_registry(self): existing_mdm2_uuid: "MDM2", # MDM2 already has a UUID elsewhere complex_uuid: "MDM2:TP53", } - edges: List[Dict[str, Any]] = [] + edges: List[Dict[str, Any]] = self._root_edge(complex_uuid) with patch('src.neo4j_connector.get_labels', return_value=["Complex"]), \ @@ -366,15 +379,9 @@ def test_leaf_reuses_uuid_when_entity_already_in_registry(self): return_value={"MDM2", "TP53"}): _emit_boundary_decomposition_edges( pathway_logic_network_data=edges, - root_input_eids={"MDM2:TP53"}, - terminal_output_eids=set(), - root_input_uuid_cache={"MDM2:TP53": complex_uuid}, - terminal_output_uuid_cache={}, reactome_id_to_uuid=reactome_id_to_uuid, ) - # Find the assembly edge whose target is the complex and whose - # source maps back to MDM2. mdm2_assembly = [ e for e in edges if e["edge_type"] == "assembly" @@ -393,7 +400,7 @@ def test_leaf_mints_fresh_uuid_when_entity_is_new_to_network(self): """ complex_uuid = "u-complex" reactome_id_to_uuid: Dict[str, str] = {complex_uuid: "C"} - edges: List[Dict[str, Any]] = [] + edges: List[Dict[str, Any]] = self._root_edge(complex_uuid) with patch('src.neo4j_connector.get_labels', return_value=["Complex"]), \ @@ -401,16 +408,197 @@ def test_leaf_mints_fresh_uuid_when_entity_is_new_to_network(self): return_value={"L1", "L2"}): _emit_boundary_decomposition_edges( pathway_logic_network_data=edges, - root_input_eids={"C"}, - terminal_output_eids=set(), - root_input_uuid_cache={"C": complex_uuid}, - terminal_output_uuid_cache={}, reactome_id_to_uuid=reactome_id_to_uuid, ) - # Two new leaf UUIDs added to the mapping new_uuids = [u for u, sid in reactome_id_to_uuid.items() if sid in {"L1", "L2"}] assert len(new_uuids) == 2 - # Each fresh UUID is also used as a source on an assembly edge for u in new_uuids: assert any(e["source_id"] == u and e["edge_type"] == "assembly" for e in edges) + + def test_same_complex_at_many_roots_shares_one_member_node(self): + """A complex appearing as a root input at N positions yields ONE member + node with N assembly edges — members are not duplicated (the user's + requirement).""" + c1, c2, c3 = "u-c1", "u-c2", "u-c3" # three root-input occurrences + reactome_id_to_uuid: Dict[str, str] = { + c1: "MDC1c", c2: "MDC1c", c3: "MDC1c", # same complex stId, 3 UUIDs + } + edges: List[Dict[str, Any]] = [] + for c in (c1, c2, c3): + edges += self._root_edge(c) + + with patch('src.neo4j_connector.get_labels', return_value=["Complex"]), \ + patch('src.logic_network_generator.get_terminal_components', + return_value={"MDC1"}): + _emit_boundary_decomposition_edges( + pathway_logic_network_data=edges, + reactome_id_to_uuid=reactome_id_to_uuid, + ) + + asm = [e for e in edges if e["edge_type"] == "assembly"] + member_uuids = {e["source_id"] for e in asm} + complex_targets = {e["target_id"] for e in asm} + assert len(member_uuids) == 1, "MDC1 member must be a single shared node" + assert complex_targets == {c1, c2, c3}, "one assembly edge to each occurrence" + + def test_intermediate_occurrence_not_decomposed(self): + """A complex that is produced and consumed (intermediate) is left intact.""" + cx = "u-cx" + reactome_id_to_uuid: Dict[str, str] = {cx: "INT"} + # cx is both a target (produced) and a source (consumed) → intermediate. + edges: List[Dict[str, Any]] = [ + {"source_id": "u-rxn1", "target_id": cx, "pos_neg": "pos", + "and_or": "and", "edge_type": "output", "stoichiometry": 1}, + {"source_id": cx, "target_id": "u-rxn2", "pos_neg": "pos", + "and_or": "and", "edge_type": "input", "stoichiometry": 1}, + ] + with patch('src.neo4j_connector.get_labels', return_value=["Complex"]), \ + patch('src.logic_network_generator.get_terminal_components', + return_value={"M1", "M2"}): + _emit_boundary_decomposition_edges( + pathway_logic_network_data=edges, + reactome_id_to_uuid=reactome_id_to_uuid, + ) + assert not any(e["edge_type"] in ("assembly", "dissociation") for e in edges), \ + "intermediate complex must not be decomposed" + + def test_dissociation_members_are_separate_readout_sinks(self): + """Terminal-output complex members come out as FRESH sink nodes — not the + member's functional node — with no outgoing edges, so the complex can't + inject its activity into the member's other roles.""" + functional_mdc1 = "u-functional-mdc1" + term_complex = "u-termc" + reactome_id_to_uuid: Dict[str, str] = { + functional_mdc1: "MDC1", # MDC1 already a functional node + term_complex: "MDC1:partner", # the terminal-output complex + } + edges: List[Dict[str, Any]] = [ + # functional MDC1 is intermediate (produced + consumed) → left intact + {"source_id": "u-rxn0", "target_id": functional_mdc1, "pos_neg": "pos", + "and_or": "and", "edge_type": "output", "stoichiometry": 1}, + {"source_id": functional_mdc1, "target_id": "u-rxn2", "pos_neg": "pos", + "and_or": "and", "edge_type": "input", "stoichiometry": 1}, + # term_complex is produced, never consumed → terminal output + {"source_id": "u-rxn", "target_id": term_complex, "pos_neg": "pos", + "and_or": "and", "edge_type": "output", "stoichiometry": 1}, + ] + with patch('src.neo4j_connector.get_labels', return_value=["Complex"]), \ + patch('src.logic_network_generator.get_terminal_components', + return_value={"MDC1", "PARTNER"}): + _emit_boundary_decomposition_edges( + pathway_logic_network_data=edges, + reactome_id_to_uuid=reactome_id_to_uuid, + ) + diss = [e for e in edges if e["edge_type"] == "dissociation"] + assert len(diss) == 2, "one readout sink per member" + sinks = {e["target_id"] for e in diss} + assert functional_mdc1 not in sinks, \ + "dissociation must NOT reuse the member's functional node" + all_sources = {e["source_id"] for e in edges} + for s in sinks: + assert s not in all_sources, "readout sink must have no outgoing edges" + assert reactome_id_to_uuid[s] in {"MDC1", "PARTNER"}, \ + "sink must carry the member's stId for measurement" + + +class TestEntityReactionProxyMapping: + """Tests for export_entity_reaction_proxy_mapping. + + A curated species (often a Complex containing an EntitySet) gets expanded + into virtual variants during generation, so its own stId never appears in + stid_to_uuid_mapping.csv. This export restores addressability by pointing + the species at the UUIDs of the reaction that produces it. + """ + + def _write(self, tmp_path, network, reaction_id_map, reactome_id_to_uuid, + participating, entity_reactions): + out = tmp_path / "proxy.csv" + with patch('src.neo4j_connector.get_pathway_participating_entities', + return_value=participating), \ + patch('src.neo4j_connector.get_pathway_entity_reactions', + return_value=entity_reactions): + export_entity_reaction_proxy_mapping( + network, reaction_id_map, reactome_id_to_uuid, + "R-HSA-1", str(out), + ) + return pd.read_csv(out) + + # Fake UUIDs must contain a dash: export_*_mapping detects the dict + # orientation with the same `'-' in key` heuristic the rest of the module + # uses, and real position-aware UUIDs always have dashes. + def test_missing_complex_maps_to_producing_reaction(self, tmp_path): + # Network: reaction R1 (uuid u-r1) outputs the expanded variant nodes; + # the parent complex C is absent from the mapping. + network = pd.DataFrame({ + "source_id": ["u-in", "u-r1"], + "target_id": ["u-r1", "u-out"], + }) + reaction_id_map = pd.DataFrame({"uid": ["u-r1"], "reactome_id": ["R-HSA-R1"]}) + # Mapping has the reaction and some leaves, but NOT complex C. + reactome_id_to_uuid = {"u-r1": "R-HSA-R1", "u-in": "R-HSA-IN", "u-out": "R-HSA-OUT"} + + df = self._write( + tmp_path, network, reaction_id_map, reactome_id_to_uuid, + participating={"R-HSA-C", "R-HSA-IN", "R-HSA-OUT"}, + entity_reactions={"R-HSA-C": {"output": ["R-HSA-R1"]}}, + ) + rows = df[df["entity_stable_id"] == "R-HSA-C"] + assert list(rows["proxy_uuid"]) == ["u-r1"] + assert set(rows["proxy_role"]) == {"producing"} + + def test_entity_already_in_mapping_is_skipped(self, tmp_path): + network = pd.DataFrame({"source_id": ["u-r1"], "target_id": ["u-out"]}) + reaction_id_map = pd.DataFrame({"uid": ["u-r1"], "reactome_id": ["R-HSA-R1"]}) + # R-HSA-OUT is directly present, so it must not be proxied. + reactome_id_to_uuid = {"u-r1": "R-HSA-R1", "u-out": "R-HSA-OUT"} + df = self._write( + tmp_path, network, reaction_id_map, reactome_id_to_uuid, + participating={"R-HSA-OUT"}, + entity_reactions={"R-HSA-OUT": {"output": ["R-HSA-R1"]}}, + ) + assert df.empty or "R-HSA-OUT" not in set(df["entity_stable_id"]) + + def test_consuming_fallback_when_no_producer(self, tmp_path): + network = pd.DataFrame({"source_id": ["u-r1"], "target_id": ["u-out"]}) + reaction_id_map = pd.DataFrame({"uid": ["u-r1"], "reactome_id": ["R-HSA-R1"]}) + reactome_id_to_uuid = {"u-r1": "R-HSA-R1", "u-out": "R-HSA-OUT"} + # C is only ever consumed (input) — no producing reaction in-pathway. + df = self._write( + tmp_path, network, reaction_id_map, reactome_id_to_uuid, + participating={"R-HSA-C"}, + entity_reactions={"R-HSA-C": {"input": ["R-HSA-R1"]}}, + ) + rows = df[df["entity_stable_id"] == "R-HSA-C"] + assert list(rows["proxy_uuid"]) == ["u-r1"] + assert set(rows["proxy_role"]) == {"consuming"} + + def test_producing_preferred_over_consuming(self, tmp_path): + network = pd.DataFrame({ + "source_id": ["u-r1", "u-r2"], "target_id": ["u-x", "u-y"], + }) + reaction_id_map = pd.DataFrame( + {"uid": ["u-r1", "u-r2"], "reactome_id": ["R-HSA-R1", "R-HSA-R2"]}) + reactome_id_to_uuid = {"u-r1": "R-HSA-R1", "u-r2": "R-HSA-R2"} + # C is produced by R1 and consumed by R2 — only the producer should win. + df = self._write( + tmp_path, network, reaction_id_map, reactome_id_to_uuid, + participating={"R-HSA-C"}, + entity_reactions={"R-HSA-C": {"output": ["R-HSA-R1"], "input": ["R-HSA-R2"]}}, + ) + rows = df[df["entity_stable_id"] == "R-HSA-C"] + assert list(rows["proxy_uuid"]) == ["u-r1"] + assert set(rows["proxy_role"]) == {"producing"} + + def test_reaction_not_in_network_yields_no_row(self, tmp_path): + # The producing reaction exists in Reactome but its UUID never made it + # into the network (e.g. dropped for having no I/O) — nothing to proxy. + network = pd.DataFrame({"source_id": ["u-a"], "target_id": ["u-b"]}) + reaction_id_map = pd.DataFrame({"uid": [], "reactome_id": []}) + reactome_id_to_uuid = {"u-a": "R-HSA-A", "u-b": "R-HSA-B"} + df = self._write( + tmp_path, network, reaction_id_map, reactome_id_to_uuid, + participating={"R-HSA-C"}, + entity_reactions={"R-HSA-C": {"output": ["R-HSA-MISSING"]}}, + ) + assert df.empty or "R-HSA-C" not in set(df["entity_stable_id"]) diff --git a/tests/test_network_invariants.py b/tests/test_network_invariants.py index f511adb..400e66a 100644 --- a/tests/test_network_invariants.py +++ b/tests/test_network_invariants.py @@ -70,7 +70,10 @@ def test_no_null_source_or_target(self, network): def test_valid_edge_types(self, network): """All edge_type values must be valid.""" - valid_edge_types = {'input', 'output', 'catalyst', 'regulator'} + valid_edge_types = { + "input", "output", "catalyst", "regulator", + "depletion", "assembly", "dissociation", "handoff", + } actual = set(network['edge_type'].unique()) invalid = actual - valid_edge_types assert len(invalid) == 0, f"Invalid edge_type values: {invalid}" diff --git a/tests/test_pathway_validation.py b/tests/test_pathway_validation.py index d88ab21..a7b641d 100644 --- a/tests/test_pathway_validation.py +++ b/tests/test_pathway_validation.py @@ -146,7 +146,10 @@ def test_logic_network_has_valid_structure(self, pathway_files): valid_pos_neg = {'pos', 'neg'} assert set(logic_network['pos_neg'].dropna().unique()).issubset(valid_pos_neg) - valid_edge_types = {'input', 'output', 'catalyst', 'regulator'} + valid_edge_types = { + "input", "output", "catalyst", "regulator", + "depletion", "assembly", "dissociation", "handoff", + } assert set(logic_network['edge_type'].unique()).issubset(valid_edge_types) def test_regulators_present(self, graph, pathway_files): diff --git a/tests/test_provenance_exports.py b/tests/test_provenance_exports.py new file mode 100644 index 0000000..59f74d1 --- /dev/null +++ b/tests/test_provenance_exports.py @@ -0,0 +1,67 @@ +"""Unit tests for the schema-backed provenance exports (nodes.csv, +node_reaction_context.csv). No Neo4j: entity labels / terminal components are +mocked. See schema/logic_network.linkml.yaml.""" +import pandas as pd + +import src.logic_network_generator as m +from src import neo4j_connector + + +def test_export_nodes_classifies_kinds(tmp_path, monkeypatch): + # simple entity labels; variant set-derivation stubbed out (no Neo4j) + monkeypatch.setattr(neo4j_connector, "get_labels", + lambda e: ["EntityWithAccessionedSequence"]) + monkeypatch.setattr(m, "_derive_sets_and_chosen", + lambda parent, members: (["R-HSA-75202"], ["R-HSA-68891"])) + monkeypatch.setattr(m, "get_terminal_components", lambda s: {s}) + + variant = "R-HSA-141608::variant::R-HSA-68365_R-HSA-68891" + # Realistic UUIDs (export_nodes detects mapping direction by UUID shape). + s1 = "aaaaaaaa-0000-0000-0000-000000000001" + v1 = "aaaaaaaa-0000-0000-0000-000000000002" + d1 = "aaaaaaaa-0000-0000-0000-000000000003" + rxn1 = "aaaaaaaa-0000-0000-0000-0000000000r1" + edges = pd.DataFrame([ + {"source_id": s1, "target_id": rxn1, "pos_neg": "pos", "and_or": "and", + "edge_type": "input", "stoichiometry": 1, "edge_reaction_id": "R-HSA-100"}, + {"source_id": rxn1, "target_id": v1, "pos_neg": "pos", "and_or": None, + "edge_type": "output", "stoichiometry": 1, "edge_reaction_id": "R-HSA-100"}, + {"source_id": v1, "target_id": d1, "pos_neg": "pos", "and_or": "and", + "edge_type": "dissociation", "stoichiometry": 1, "edge_reaction_id": None}, + ]) + reaction_id_map = pd.DataFrame({"uid": [rxn1], "reactome_id": ["R-HSA-100"]}) + uuid_mapping = {s1: "R-HSA-999", v1: variant, d1: "R-HSA-888"} + + out = tmp_path / "nodes.csv" + m.export_nodes(edges, reaction_id_map, uuid_mapping, str(out)) + rows = {r["uuid"]: r for r in pd.read_csv(out, dtype=str, keep_default_na=False) + .to_dict("records")} + assert rows[rxn1]["node_kind"] == "reaction" + assert rows[rxn1]["diagram_entity_id"] == "R-HSA-100" + assert rows[v1]["node_kind"] == "set_variant" + assert rows[v1]["diagram_entity_id"] == "R-HSA-141608" + assert "R-HSA-68891" in rows[v1]["member_leaves"] + assert rows[s1]["node_kind"] == "simple_entity" + assert rows[d1]["node_kind"] == "dissociation_sink" + + +def test_export_node_reaction_context(tmp_path): + entity_uuid_registry = { + ("R-HSA-999", "rxn1", "input"): "s1", + ("R-HSA-141608::variant::x", "rxn1", "output"): "v1", + } + reaction_id_map = pd.DataFrame({"uid": ["rxn1"], "reactome_id": ["R-HSA-100"]}) + catreg = pd.DataFrame([{"reaction_id": "R-HSA-100", "entity_id": "R-HSA-7", + "edge_type": "catalyst", "uuid": "c1", "reaction_uuid": "rxn1"}]) + out = tmp_path / "ctx.csv" + m.export_node_reaction_context(entity_uuid_registry, reaction_id_map, catreg, str(out)) + ctx = pd.read_csv(out).to_dict("records") + triples = {(r["context_node"], r["reaction_id"], r["role"]) for r in ctx} + assert ("s1", "R-HSA-100", "input") in triples + assert ("v1", "R-HSA-100", "output") in triples + assert ("c1", "R-HSA-100", "catalyst") in triples + + +def test_parse_variant_members(): + assert m._parse_variant_members("R-HSA-1::variant::R-HSA-2_R-HSA-3") == {"R-HSA-2", "R-HSA-3"} + assert m._parse_variant_members("R-HSA-1") == set() diff --git a/tests/test_regulators_and_catalysts.py b/tests/test_regulators_and_catalysts.py index 39c0a9e..8885ff7 100644 --- a/tests/test_regulators_and_catalysts.py +++ b/tests/test_regulators_and_catalysts.py @@ -24,7 +24,7 @@ from src.logic_network_generator import append_regulators -def _mock_decompose(entity_id): +def _mock_decompose(entity_id, variant_decomposition=False): """Return entity as-is (no decomposition) for unit tests.""" return [(entity_id, 1)] diff --git a/tests/test_uid_reaction_connections.py b/tests/test_uid_reaction_connections.py index 5520ed2..a4bcbc0 100644 --- a/tests/test_uid_reaction_connections.py +++ b/tests/test_uid_reaction_connections.py @@ -146,7 +146,11 @@ def test_pathway_has_valid_structure(self, pathway_dir): assert len(logic_network) > 0, "Logic network is empty" - valid_edge_types = {"input", "output", "catalyst", "regulator"} + # Must match the EdgeType enum in schema/logic_network.linkml.yaml. + valid_edge_types = { + "input", "output", "catalyst", "regulator", + "depletion", "assembly", "dissociation", "handoff", + } actual_types = set(logic_network["edge_type"].unique()) invalid = actual_types - valid_edge_types assert len(invalid) == 0, f"Invalid edge_type values: {invalid}"