diff --git a/graphify/extract.py b/graphify/extract.py index 8b1d3dadb..004b748ae 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -54,6 +54,7 @@ from graphify.extractors.sql import extract_sql # noqa: F401 from graphify.extractors.terraform import extract_terraform # noqa: F401 from graphify.extractors.verilog import extract_verilog # noqa: F401 +from graphify.extractors.yaml_config import extract_yaml # noqa: F401 from graphify.extractors.zig import extract_zig # noqa: F401 from graphify.security import sanitize_metadata from graphify.paths import disambiguate_ambiguous_candidates @@ -4278,6 +4279,8 @@ def add_existing_edge(edge: dict) -> None: ".sh": extract_bash, ".bash": extract_bash, ".json": extract_json, + ".yaml": extract_yaml, + ".yml": extract_yaml, ".tf": extract_terraform, ".tfvars": extract_terraform, ".hcl": extract_terraform, @@ -4305,6 +4308,8 @@ def add_existing_edge(edge: dict) -> None: # extract() to tell the user which extra restores the language. _EXTRA_FOR_EXTENSION = { ".sql": "sql", + ".yaml": "yaml", + ".yml": "yaml", ".tf": "terraform", ".tfvars": "terraform", ".hcl": "terraform", diff --git a/graphify/extractors/yaml_config.py b/graphify/extractors/yaml_config.py new file mode 100644 index 000000000..ab1d5e34b --- /dev/null +++ b/graphify/extractors/yaml_config.py @@ -0,0 +1,320 @@ +"""Yaml_config extractor. Docker Compose and GitHub Actions workflows.""" +from __future__ import annotations + + +from pathlib import Path +from graphify.extractors.base import _file_stem, _make_id + + +# Filenames that are Docker Compose files by convention. Matched before the +# top-level key probe below, the same cheap-first order json_config uses. +_COMPOSE_PREFIXES = ("docker-compose", "compose") + +# Step keys that carry a reference to another workflow/action rather than a +# shell command. `uses` is the only one today, but keeping the set makes the +# intent explicit at the call site. +_USES_KEYS = frozenset({"uses"}) + + +def _descend(node, wanted: frozenset[str]): + """Return the first descendant of *node* whose type is in *wanted*. + + YAML wraps every value in ``block_node``/``flow_node`` before the actual + collection, and a document adds another layer, so callers would otherwise + repeat the same two-or-three-step unwrap everywhere. + """ + if node is None: + return None + if node.type in wanted: + return node + for child in node.children: + if not child.is_named: + continue + if child.type in ("block_node", "flow_node", "document"): + found = _descend(child, wanted) + if found is not None: + return found + elif child.type in wanted: + return child + return None + + +_MAPPING_TYPES = frozenset({"block_mapping", "flow_mapping"}) +_SEQUENCE_TYPES = frozenset({"block_sequence", "flow_sequence"}) + + +def _mapping(node): + return _descend(node, _MAPPING_TYPES) + + +def _pairs(node): + """Yield ``(key, value_node, line)`` for each pair of the mapping at *node*. + + *node* may be the mapping itself or any wrapper around it. Pairs whose key + is not a plain scalar (rare — a complex mapping key) are skipped rather + than stringified, so they never mint a garbage node. + """ + mapping = _mapping(node) + if mapping is None: + return + for pair in mapping.children: + if pair.type not in ("block_mapping_pair", "flow_pair"): + continue + key_node = pair.child_by_field_name("key") + if key_node is None: + continue + key = _scalar_text(key_node) + if not key: + continue + yield key, pair.child_by_field_name("value"), key_node.start_point[0] + 1 + + +def _item_value(item): + """The value inside a ``block_sequence_item``, without the ``- `` marker. + + ``item.text`` spans the marker too, so reading it directly yields ``"- api"`` + where the dependency is ``api``. + """ + if item.type != "block_sequence_item": + return item + for child in item.children: + if child.is_named: + return child + return item + + +def _scalar_text(node) -> str: + """Text of the scalar at *node*, with one layer of quotes stripped.""" + if node is None: + return "" + text = node.text.decode("utf-8", errors="replace").strip() + if len(text) >= 2 and text[0] == text[-1] and text[0] in ("'", '"'): + text = text[1:-1] + return text.strip() + + +def _string_items(node) -> list[tuple[str, int]]: + """Scalars reachable from *node* as ``(text, line)``. + + Handles the three shapes a Compose/Actions dependency list takes: a bare + scalar (``needs: build``), a sequence (``needs: [build, test]``), and a + mapping whose KEYS are the dependencies (Compose's long-form + ``depends_on: {db: {condition: ...}}``). + """ + if node is None: + return [] + seq = _descend(node, _SEQUENCE_TYPES) + if seq is not None: + items = [] + for item in seq.children: + if item.type not in ("block_sequence_item", "flow_node"): + continue + text = _scalar_text(_item_value(item)) + # A sequence item wrapping a mapping is a step, not a name. + if text and "\n" not in text and ":" not in text: + items.append((text, item.start_point[0] + 1)) + return items + mapping = _mapping(node) + if mapping is not None: + return [(key, line) for key, _value, line in _pairs(mapping)] + text = _scalar_text(node) + return [(text, node.start_point[0] + 1)] if text else [] + + +def _sequence_items(node): + """Yield the item nodes of the sequence at *node* (for step lists).""" + seq = _descend(node, _SEQUENCE_TYPES) + if seq is None: + return + for item in seq.children: + if item.type in ("block_sequence_item", "flow_node"): + yield item + + +def _top_level(root): + """The document's top-level mapping, or None when the file is not a mapping.""" + for doc in root.children: + if doc.type != "document": + continue + mapping = _mapping(doc) + if mapping is not None: + return mapping + return _mapping(root) + + +def _kind(path: Path, top) -> str | None: + """Classify a YAML file as ``compose``, ``workflow``, or None. + + None means "data YAML" — an OpenAPI spec, a k8s manifest, a fixture — which + has no dependency structure this extractor models. Those return an empty + result and stay with the semantic pass, exactly as _is_config_json leaves + data JSON to it (#1224). + """ + if top is None: + return None + keys = {key for key, _value, _line in _pairs(top)} + name = path.name.casefold() + parts = [p.casefold() for p in path.parts] + + if "jobs" in keys and ("on" in keys or "workflows" in parts): + return "workflow" + if "services" in keys: + if name.startswith(_COMPOSE_PREFIXES) or "version" in keys or "networks" in keys or "volumes" in keys: + return "compose" + # A bare `services:` mapping whose values are mappings is still Compose + # shaped; require the mapping so a `services: [a, b]` list in some + # unrelated config does not get mistaken for one. + for key, value, _line in _pairs(top): + if key == "services" and _mapping(value) is not None: + return "compose" + return None + + +def extract_yaml(path: Path) -> dict: + """Extract Docker Compose services and GitHub Actions jobs via tree-sitter. + + Nodes: Compose services, Actions jobs, and the actions/reusable workflows a + job `uses`. Edges: `contains` (file -> service/job), `depends_on` (Compose + `depends_on`/`extends`, Actions `needs`), and `uses` (job/step -> action). + + Definitions are file-scoped (`_make_id(stem, name)`) and carry a `contains` + edge. Cross-file references — a `depends_on` naming a service an overlay + file defines, or an `actions/checkout@v4` shared by every workflow — are + minted as SOURCELESS stubs (`_make_id(name)`, no `contains`), the same + pattern the SQL and Go extractors use (#2324, #1402), so + `_rewire_unique_stub_nodes` can collapse them onto the real definition and + an unresolved name still survives as a portable node instead of dangling. + + Data YAML (k8s manifests, OpenAPI specs, fixtures) returns an empty result + and is left to the semantic pass. + """ + # Lockfiles (pnpm-lock.yaml, conda envs) reach tens of MB and never carry a + # `services`/`jobs` section, so parsing them is pure cost. Same ceiling and + # same bounded read as extract_json, which reads one byte past the limit so + # a file growing between stat and read cannot slip through (#1224). + _YAML_MAX_BYTES = 1_048_576 # 1 MiB + + try: + import tree_sitter_yaml as tsyaml + from tree_sitter import Language, Parser + except ImportError: + return {"nodes": [], "edges": [], "error": "tree_sitter_yaml not installed. Run: pip install tree-sitter-yaml"} + + try: + with path.open("rb") as fh: + source = fh.read(_YAML_MAX_BYTES + 1) + if len(source) > _YAML_MAX_BYTES: + return {"nodes": [], "edges": [], "error": "yaml file too large to index"} + language = Language(tsyaml.language()) + parser = Parser(language) + tree = parser.parse(source) + root = tree.root_node + except Exception as e: + return {"nodes": [], "edges": [], "error": str(e)} + + top = _top_level(root) + kind = _kind(path, top) + if kind is None: + return {"nodes": [], "edges": []} + + str_path = str(path) + stem = _file_stem(path) + file_nid = _make_id(str_path) + + nodes: list[dict] = [{"id": file_nid, "label": path.name, "file_type": "code", + "source_file": str_path, "source_location": None}] + edges: list[dict] = [] + seen_ids: set[str] = {file_nid} + seen_edges: set[tuple[str, str, str]] = set() + # name -> nid for the definitions in THIS file, so a local reference binds + # to the real node instead of minting a stub next to it. + local_nids: dict[str, str] = {} + + def _add_definition(name: str, line: int) -> str: + nid = _make_id(stem, name) + if nid not in seen_ids: + seen_ids.add(nid) + nodes.append({"id": nid, "label": name, "file_type": "code", + "source_file": str_path, "source_location": f"L{line}"}) + edges.append({"source": file_nid, "target": nid, "relation": "contains", + "confidence": "EXTRACTED", "source_file": str_path, + "source_location": f"L{line}", "weight": 1.0}) + local_nids[name] = nid + return nid + + def _ref_stub(name: str, *, external: bool = False) -> str: + nid = _make_id(name) + if nid not in seen_ids: + seen_ids.add(nid) + node = {"id": nid, "label": name, "file_type": "code", + "source_file": "", "source_location": "", + "origin_file": str_path} + if external: + # `actions/checkout@v4` referenced by ten workflows is ONE action, + # not ten same-named symbols — the module-anchor case + # _disambiguate_colliding_node_ids is explicitly exempt from + # (#1327). Without the exemption each workflow's stub gets salted + # with its own path and the shared action scatters into N nodes + # instead of becoming the hub that makes "who uses this action" + # answerable. + node["type"] = "module" + nodes.append(node) + return nid + + def _add_edge(src: str, name: str, relation: str, line: int) -> None: + tgt = local_nids.get(name) or _ref_stub(name, external=relation == "uses") + if src == tgt: + return + key = (src, tgt, relation) + if key in seen_edges: + return + seen_edges.add(key) + edges.append({"source": src, "target": tgt, "relation": relation, + "confidence": "EXTRACTED", "source_file": str_path, + "source_location": f"L{line}", "weight": 1.0}) + + section = "services" if kind == "compose" else "jobs" + entries = [(key, value, line) for key, value, line in _pairs(top) if key == section] + if not entries: + return {"nodes": nodes, "edges": edges} + + # Pass 1: every definition first, so a forward reference (a service that + # depends_on one declared later in the file) binds locally instead of + # minting a stub that would then compete with the real node. + members = [(name, body, line) for _k, value, _l in entries + for name, body, line in _pairs(value)] + for name, _body, line in members: + _add_definition(name, line) + + # Pass 2: the references. + for name, body, _line in members: + owner = local_nids[name] + for key, value, line in _pairs(body): + if key in ("depends_on", "needs"): + for dep, dep_line in _string_items(value): + _add_edge(owner, dep, "depends_on", dep_line) + elif key == "extends": + # `extends: {service: base}` — a mapping naming the base + # service; `extends: base` shorthand is a bare scalar. + target = "" + for sub_key, sub_value, _sub_line in _pairs(value): + if sub_key == "service": + target = _scalar_text(sub_value) + if not target: + target = _scalar_text(value) + if target: + _add_edge(owner, target, "depends_on", line) + elif key in _USES_KEYS: + # Job-level `uses:` — a reusable workflow call. + target = _scalar_text(value) + if target: + _add_edge(owner, target, "uses", line) + elif key == "steps": + for item in _sequence_items(value): + for step_key, step_value, step_line in _pairs(item): + if step_key in _USES_KEYS: + target = _scalar_text(step_value) + if target: + _add_edge(owner, target, "uses", step_line) + + return {"nodes": nodes, "edges": edges} diff --git a/pyproject.toml b/pyproject.toml index 6288b9c8a..dae6218f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,11 @@ gemini = ["openai", "tiktoken"] openai = ["openai", "tiktoken"] chinese = ["jieba"] sql = ["tree-sitter-sql"] +# extract_yaml() models Docker Compose services and GitHub Actions jobs. YAML +# stays a DOC_EXTENSIONS document either way, so without this extra the file is +# still read by the semantic pass — the extra only adds the free, deterministic +# structural edges on top, which is why it is optional rather than a base dep. +yaml = ["tree-sitter-yaml"] # extract_pascal() uses tree-sitter-pascal for AST-quality extraction (more # accurate calls/inherits edges) and falls back to a regex extractor when it is # absent (#781), so this stays optional. Unlike tree-sitter-dm below, it ships @@ -85,7 +90,7 @@ pascal = ["tree-sitter-pascal"] # avoids breaking the default `uv tool install graphifyy` for everyone (#1104). dm = ["tree-sitter-dm"] terraform = ["tree-sitter-hcl"] -all = ["mcp>=1,<3", "starlette>=1.3.1,<2", "neo4j", "falkordb", "pypdf>=6.12.0", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "python-docx", "openpyxl", "faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.6.9", "matplotlib", "numpy>=2.0; python_version >= '3.13'", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "jieba", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-pascal"] +all = ["mcp>=1,<3", "starlette>=1.3.1,<2", "neo4j", "falkordb", "pypdf>=6.12.0", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "python-docx", "openpyxl", "faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.6.9", "matplotlib", "numpy>=2.0; python_version >= '3.13'", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "tree-sitter-yaml", "jieba", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-pascal"] [project.scripts] graphify = "graphify.__main__:main" diff --git a/tests/test_yaml_config.py b/tests/test_yaml_config.py new file mode 100644 index 000000000..eb71cb291 --- /dev/null +++ b/tests/test_yaml_config.py @@ -0,0 +1,278 @@ +"""Tests for the YAML extractor (graphify/extractors/yaml_config.py). + +Covers the two shapes extract_yaml models — Docker Compose services and GitHub +Actions workflows — plus the deliberate non-coverage of data YAML, which stays +with the semantic pass. +""" +from __future__ import annotations + +from pathlib import Path + +import pytest + +from graphify.build import build_from_json +from graphify.extract import extract, extract_yaml + +def _write(tmp_path: Path, name: str, body: str) -> Path: + p = tmp_path / name + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(body, encoding="utf-8") + return p + + +def _labels(r) -> list[str]: + return [n["label"] for n in r["nodes"]] + + +def _rel_pairs(r, relation: str) -> set[tuple[str, str]]: + lab = {n["id"]: n["label"] for n in r["nodes"]} + return { + (lab.get(e["source"], e["source"]), lab.get(e["target"], e["target"])) + for e in r["edges"] + if e["relation"] == relation + } + + +COMPOSE = """\ +# leading comment so the mapping is not children[0] +services: + api: + image: api:latest + depends_on: + redis: + condition: service_healthy + db: + condition: service_started + web: + build: ./web + depends_on: + - api + worker: + extends: + service: api + redis: + image: redis:7 + db: + image: postgres:16 + +volumes: + pgdata: +""" + +WORKFLOW = """\ +name: CI +on: + push: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: pnpm lint + test: + needs: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + deploy: + needs: [lint, test] + uses: ./.github/workflows/release.yml +""" + + +@pytest.fixture(autouse=True) +def _require_grammar(): + pytest.importorskip("tree_sitter_yaml") + + +# ── Docker Compose ──────────────────────────────────────────────────────────── + +def test_compose_services_become_nodes(tmp_path): + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + assert r.get("error") is None + labels = set(_labels(r)) + for expected in ("api", "web", "worker", "redis", "db"): + assert expected in labels, f"missing service node {expected!r}" + + +def test_compose_file_contains_services(tmp_path): + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + contains = _rel_pairs(r, "contains") + assert ("docker-compose.yml", "api") in contains + assert ("docker-compose.yml", "redis") in contains + + +def test_compose_depends_on_long_form_mapping(tmp_path): + # `depends_on: {redis: {condition: ...}}` — the dependency names are the KEYS. + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + deps = _rel_pairs(r, "depends_on") + assert ("api", "redis") in deps + assert ("api", "db") in deps + + +def test_compose_depends_on_list_form_strips_sequence_marker(tmp_path): + # `depends_on: [- api]` — the block_sequence_item text spans the "- " marker, + # so reading it raw yields "- api" and mints a bogus node. + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + assert ("web", "api") in _rel_pairs(r, "depends_on") + assert not any(lbl.startswith("- ") for lbl in _labels(r)) + + +def test_compose_extends_service(tmp_path): + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + assert ("worker", "api") in _rel_pairs(r, "depends_on") + + +def test_compose_forward_reference_binds_locally(tmp_path): + # `api` depends on `redis`, which is declared LATER in the file. The + # definition pass must run first, or the reference mints a stub that then + # competes with the real node. + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + real_redis = next(n["id"] for n in r["nodes"] + if n["label"] == "redis" and n.get("source_file")) + dep_targets = {e["target"] for e in r["edges"] if e["relation"] == "depends_on"} + assert real_redis in dep_targets + assert len([n for n in r["nodes"] if n["label"] == "redis"]) == 1 + + +# ── GitHub Actions ──────────────────────────────────────────────────────────── + +def test_workflow_jobs_become_nodes(tmp_path): + r = extract_yaml(_write(tmp_path, "ci.yml", WORKFLOW)) + assert r.get("error") is None + labels = set(_labels(r)) + for expected in ("lint", "test", "deploy"): + assert expected in labels, f"missing job node {expected!r}" + + +def test_workflow_needs_becomes_depends_on(tmp_path): + r = extract_yaml(_write(tmp_path, "ci.yml", WORKFLOW)) + deps = _rel_pairs(r, "depends_on") + assert ("test", "lint") in deps # scalar form: `needs: lint` + assert ("deploy", "lint") in deps # list form: `needs: [lint, test]` + assert ("deploy", "test") in deps + + +def test_workflow_step_uses_edges(tmp_path): + r = extract_yaml(_write(tmp_path, "ci.yml", WORKFLOW)) + uses = _rel_pairs(r, "uses") + assert ("lint", "actions/checkout@v4") in uses + assert ("lint", "actions/setup-node@v4") in uses + + +def test_workflow_reusable_workflow_uses_edge(tmp_path): + # Job-level `uses:` is a reusable-workflow call, not a step. + r = extract_yaml(_write(tmp_path, "ci.yml", WORKFLOW)) + assert ("deploy", "./.github/workflows/release.yml") in _rel_pairs(r, "uses") + + +def test_workflow_detected_by_path_without_on_key(tmp_path): + body = "jobs:\n build:\n steps:\n - uses: actions/checkout@v4\n" + p = _write(tmp_path, ".github/workflows/build.yml", body) + assert "build" in set(_labels(extract_yaml(p))) + + +def test_run_steps_do_not_become_nodes(tmp_path): + # `- run: pnpm lint` is a shell command, not a reference. + r = extract_yaml(_write(tmp_path, "ci.yml", WORKFLOW)) + assert not any("pnpm lint" in lbl for lbl in _labels(r)) + + +# ── Data YAML is deliberately not modelled ──────────────────────────────────── + +K8S = """\ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: api +spec: + replicas: 3 +""" + +OPENAPI = """\ +openapi: 3.0.0 +paths: + /users: + get: + summary: list users +""" + + +@pytest.mark.parametrize("name,body", [("deploy.yaml", K8S), ("openapi.yaml", OPENAPI)]) +def test_data_yaml_returns_empty(tmp_path, name, body): + # No dependency structure to model — left to the semantic pass, mirroring + # how _is_config_json skips data JSON (#1224). Not even a file node, so the + # file never shows up as an empty orphan in the graph. + r = extract_yaml(_write(tmp_path, name, body)) + assert r.get("error") is None + assert r["nodes"] == [] + assert r["edges"] == [] + + +def test_services_list_is_not_mistaken_for_compose(tmp_path): + # A `services:` LIST (not a mapping) is some other config's key. + body = "services:\n - alpha\n - beta\n" + r = extract_yaml(_write(tmp_path, "app.yaml", body)) + assert r["nodes"] == [] + + +def test_empty_and_comment_only_files_are_safe(tmp_path): + assert extract_yaml(_write(tmp_path, "a.yml", "")).get("error") is None + r = extract_yaml(_write(tmp_path, "b.yml", "# just a comment\n")) + assert r.get("error") is None + assert r["nodes"] == [] + + +def test_no_dangling_edge_sources(tmp_path): + r = extract_yaml(_write(tmp_path, "docker-compose.yml", COMPOSE)) + node_ids = {n["id"] for n in r["nodes"]} + for e in r["edges"]: + assert e["source"] in node_ids, f"dangling source: {e['source']}" + assert e["target"] in node_ids, f"dangling target: {e['target']}" + + +# ── Cross-file resolution ───────────────────────────────────────────────────── + +def test_overlay_depends_on_resolves_onto_base_definition(tmp_path): + """A Compose overlay referencing a service the base file defines must + collapse onto the real node via the sourceless-stub rewire, not dangle as a + second `db` node (the pattern SQL uses for cross-migration FKs, #2324).""" + base = _write(tmp_path, "docker-compose.yml", "services:\n db:\n image: postgres:16\n") + overlay = _write( + tmp_path, "docker-compose.prod.yml", + "services:\n api:\n image: api\n depends_on:\n - db\n", + ) + + r = extract([base.resolve(), overlay.resolve()], root=tmp_path) + + db_nodes = [n for n in r["nodes"] if n["label"] == "db"] + assert len(db_nodes) == 1, f"expected one db node after rewire, got {db_nodes}" + dep_targets = {e["target"] for e in r["edges"] if e["relation"] == "depends_on"} + assert db_nodes[0]["id"] in dep_targets + + +def test_shared_action_merges_across_workflows(tmp_path): + """The same action pinned by two workflows is one node, so `actions/checkout` + becomes a real hub instead of one dangling stub per file.""" + a = _write(tmp_path, ".github/workflows/a.yml", + "on: push\njobs:\n one:\n steps:\n - uses: actions/checkout@v4\n") + b = _write(tmp_path, ".github/workflows/b.yml", + "on: push\njobs:\n two:\n steps:\n - uses: actions/checkout@v4\n") + + r = extract([a.resolve(), b.resolve()], root=tmp_path) + + # Both files mint the anchor, so two dicts survive extraction sharing ONE id + # — the same shape #1327 describes for `import CoreKit` from three files. + # They collapse into a single graph node. + checkout_ids = {n["id"] for n in r["nodes"] if n["label"] == "actions/checkout@v4"} + assert len(checkout_ids) == 1, f"expected one shared action id, got {checkout_ids}" + checkout_id = checkout_ids.pop() + + G = build_from_json({"nodes": r["nodes"], "edges": r["edges"]}) + assert G.has_node(checkout_id) + sources = {e["source"] for e in r["edges"] + if e["relation"] == "uses" and e["target"] == checkout_id} + assert len(sources) == 2, "both workflows should point at the shared action node" diff --git a/uv.lock b/uv.lock index 8573a9e9d..9679f4e68 100644 --- a/uv.lock +++ b/uv.lock @@ -97,7 +97,7 @@ name = "autograd" version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/1c/3c24ec03c8ba4decc742b1df5a10c52f98c84ca8797757f313e7bdcdf276/autograd-1.8.0.tar.gz", hash = "sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683", size = 2562146, upload-time = "2025-05-05T12:49:02.502Z" } wheels = [ @@ -477,7 +477,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } wheels = [ @@ -555,7 +555,7 @@ resolution-markers = [ "python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } @@ -813,10 +813,10 @@ name = "ctranslate2" version = "4.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "setuptools", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml" }, + { name = "setuptools" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/cb/e0/b69c40c3d739b213a78d327071240590792071b4f890e34088b03b95bb1e/ctranslate2-4.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9017a355dd7c6d29dc3bca6e9fc74827306c61b702c66bb1f6b939655e7de3fa", size = 1255773, upload-time = "2026-02-04T06:11:04.769Z" }, @@ -926,7 +926,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -951,12 +951,12 @@ name = "faster-whisper" version = "1.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "av", marker = "python_full_version >= '3.11'" }, - { name = "ctranslate2", marker = "python_full_version >= '3.11'" }, - { name = "huggingface-hub", marker = "python_full_version >= '3.11'" }, - { name = "onnxruntime", marker = "python_full_version >= '3.11'" }, - { name = "tokenizers", marker = "python_full_version >= '3.11'" }, - { name = "tqdm", marker = "python_full_version >= '3.11'" }, + { name = "av" }, + { name = "ctranslate2" }, + { name = "huggingface-hub" }, + { name = "onnxruntime" }, + { name = "tokenizers" }, + { name = "tqdm" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909, upload-time = "2025-10-31T11:35:47.794Z" }, @@ -1059,10 +1059,10 @@ name = "gensim" version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "smart-open", marker = "python_full_version < '3.13'" }, + { name = "smart-open" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/80/fe9d2e1ace968041814dbcfce4e8499a643a36c41267fa4b6c4f54cce420/gensim-4.4.0.tar.gz", hash = "sha256:a3f5b626da5518e79a479140361c663089fe7998df8ba52d56e1ded71ac5bdf5", size = 23260095, upload-time = "2025-10-18T02:06:45.962Z" } wheels = [ @@ -1090,7 +1090,7 @@ wheels = [ [[package]] name = "graphifyy" -version = "0.9.31" +version = "0.9.35" source = { editable = "." } dependencies = [ { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -1149,6 +1149,7 @@ all = [ { name = "tree-sitter-hcl" }, { name = "tree-sitter-pascal" }, { name = "tree-sitter-sql" }, + { name = "tree-sitter-yaml" }, { name = "watchdog" }, { name = "yt-dlp" }, ] @@ -1226,6 +1227,9 @@ video = [ watch = [ { name = "watchdog" }, ] +yaml = [ + { name = "tree-sitter-yaml" }, +] [package.dev-dependencies] dev = [ @@ -1325,13 +1329,15 @@ requires-dist = [ { name = "tree-sitter-swift", specifier = ">=0.7,<0.9" }, { name = "tree-sitter-typescript", specifier = ">=0.23,<0.25" }, { name = "tree-sitter-verilog", specifier = ">=1.0,<2.0" }, + { name = "tree-sitter-yaml", marker = "extra == 'all'" }, + { name = "tree-sitter-yaml", marker = "extra == 'yaml'" }, { name = "tree-sitter-zig", specifier = ">=1.0,<2.0" }, { name = "watchdog", marker = "extra == 'all'" }, { name = "watchdog", marker = "extra == 'watch'" }, { name = "yt-dlp", marker = "extra == 'all'", specifier = ">=2026.6.9" }, { name = "yt-dlp", marker = "extra == 'video'", specifier = ">=2026.6.9" }, ] -provides-extras = ["mcp", "neo4j", "falkordb", "pdf", "watch", "svg", "leiden", "office", "google", "postgres", "video", "kimi", "ollama", "bedrock", "anthropic", "gemini", "openai", "chinese", "sql", "pascal", "dm", "terraform", "all"] +provides-extras = ["mcp", "neo4j", "falkordb", "pdf", "watch", "svg", "leiden", "office", "google", "postgres", "video", "kimi", "ollama", "bedrock", "anthropic", "gemini", "openai", "chinese", "sql", "yaml", "pascal", "dm", "terraform", "all"] [package.metadata.requires-dev] dev = [ @@ -1357,26 +1363,26 @@ name = "graspologic" version = "3.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anytree", marker = "python_full_version < '3.13'" }, - { name = "beartype", marker = "python_full_version < '3.13'" }, - { name = "future", marker = "python_full_version < '3.13'" }, - { name = "gensim", marker = "python_full_version < '3.13'" }, - { name = "graspologic-native", marker = "python_full_version < '3.13'" }, - { name = "hyppo", marker = "python_full_version < '3.13'" }, - { name = "joblib", marker = "python_full_version < '3.13'" }, - { name = "matplotlib", marker = "python_full_version < '3.13'" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "anytree" }, + { name = "beartype" }, + { name = "future" }, + { name = "gensim" }, + { name = "graspologic-native" }, + { name = "hyppo" }, + { name = "joblib" }, + { name = "matplotlib" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "pot", marker = "python_full_version < '3.13'" }, - { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "pot" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "seaborn", marker = "python_full_version < '3.13'" }, - { name = "statsmodels", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "umap-learn", marker = "python_full_version < '3.13'" }, + { name = "seaborn" }, + { name = "statsmodels" }, + { name = "typing-extensions" }, + { name = "umap-learn" }, ] sdist = { url = "https://files.pythonhosted.org/packages/91/bb/0fe2ef85ea775e7b8416b2cf90097aa4b5e0c9c2271d7fe6789bab27d0ca/graspologic-3.4.4.tar.gz", hash = "sha256:79878caf367da3e89046a4ec94291c5b1a5da569f19fdd879d8b45c3563d7110", size = 5134258, upload-time = "2025-09-08T21:44:01.969Z" } wheels = [ @@ -1478,15 +1484,15 @@ name = "huggingface-hub" version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock", marker = "python_full_version >= '3.11'" }, - { name = "fsspec", marker = "python_full_version >= '3.11'" }, - { name = "hf-xet", marker = "(python_full_version >= '3.11' and platform_machine == 'AMD64') or (python_full_version >= '3.11' and platform_machine == 'aarch64') or (python_full_version >= '3.11' and platform_machine == 'amd64') or (python_full_version >= '3.11' and platform_machine == 'arm64') or (python_full_version >= '3.11' and platform_machine == 'x86_64')" }, - { name = "httpx", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "tqdm", marker = "python_full_version >= '3.11'" }, - { name = "typer", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/b6/e22bd20a25299c34b8c5922c1545a6320825b13906eb0f7298edfd034a0b/huggingface_hub-1.15.0.tar.gz", hash = "sha256:28abfdddda3927fd4de6a63cf26ab012498a2c24dae52baf150c5c6edf98a1d5", size = 784100, upload-time = "2026-05-15T11:42:52.149Z" } wheels = [ @@ -1511,18 +1517,18 @@ name = "hyppo" version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "autograd", marker = "python_full_version < '3.13'" }, - { name = "future", marker = "python_full_version < '3.13'" }, - { name = "numba", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "autograd" }, + { name = "future" }, + { name = "numba" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "patsy", marker = "python_full_version < '3.13'" }, - { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "patsy" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "statsmodels", marker = "python_full_version < '3.13'" }, + { name = "statsmodels" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/a6/0d84fe8486a1447da8bdb8ebb249d525fd8c1d0fe038bceb003c6e0513f9/hyppo-0.5.2.tar.gz", hash = "sha256:4634d15516248a43d25c241ed18beeb79bb3210360f7253693b3f154fe8c9879", size = 125115, upload-time = "2025-05-24T18:33:27.418Z" } wheels = [ @@ -1552,7 +1558,7 @@ name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp", marker = "python_full_version < '3.11'" }, + { name = "zipp" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -2279,8 +2285,8 @@ name = "numba" version = "0.65.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "llvmlite", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "llvmlite" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/c5/db2ac3685833d626c0dcae6bd2330cd68433e1fd248d15f70998160d3ad7/numba-0.65.1.tar.gz", hash = "sha256:19357146c32fe9ed25059ab915e8465fb13951cf6b0aace3826b76886373ab23", size = 2765600, upload-time = "2026-04-24T02:02:56.551Z" } wheels = [ @@ -2440,11 +2446,11 @@ name = "onnxruntime" version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "flatbuffers", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "flatbuffers" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "protobuf", marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "protobuf" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/d4/81/29a9eb470994a75eb7b3ccf32be314d7c66675a00ac7b50294816cc2db27/onnxruntime-1.26.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ee1109ef4ef27cad90e823399e61e03b3c6c7bfe0fb820b4baf3678c15be8b3c", size = 18005108, upload-time = "2026-05-08T19:08:11.728Z" }, @@ -2530,10 +2536,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pytz", marker = "python_full_version < '3.11'" }, - { name = "tzdata", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } wheels = [ @@ -2596,9 +2602,9 @@ resolution-markers = [ "python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "tzdata", marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32')" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } wheels = [ @@ -2672,7 +2678,7 @@ name = "patsy" version = "1.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" } wheels = [ @@ -2855,8 +2861,8 @@ name = "pot" version = "0.9.6.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/8b/5f939eaf1fbeb7ff914fe540d659486951a056e5537b8f454362045b6c72/pot-0.9.6.post1.tar.gz", hash = "sha256:9b6cc14a8daecfe1268268168cf46548f9130976b22b24a9e8ec62a734be6c43", size = 604243, upload-time = "2025-09-22T12:51:14.894Z" } @@ -3199,12 +3205,12 @@ name = "pynndescent" version = "0.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "joblib", marker = "python_full_version < '3.13'" }, - { name = "llvmlite", marker = "python_full_version < '3.13'" }, - { name = "numba", marker = "python_full_version < '3.13'" }, - { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "joblib" }, + { name = "llvmlite" }, + { name = "numba" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4a/fb/7f58c397fb31666756457ee2ac4c0289ef2daad57f4ae4be8dec12f80b03/pynndescent-0.6.0.tar.gz", hash = "sha256:7ffde0fb5b400741e055a9f7d377e3702e02250616834231f6c209e39aac24f5", size = 2992987, upload-time = "2026-01-08T21:29:58.943Z" } @@ -3870,10 +3876,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "joblib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, + { name = "joblib" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } wheels = [ @@ -3919,10 +3925,10 @@ resolution-markers = [ "python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "joblib", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "threadpoolctl", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "joblib" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" } }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } wheels = [ @@ -3972,7 +3978,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -4033,7 +4039,7 @@ resolution-markers = [ "python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -4104,9 +4110,9 @@ name = "seaborn" version = "0.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "matplotlib", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } @@ -4146,7 +4152,7 @@ name = "smart-open" version = "7.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "wrapt", marker = "python_full_version < '3.13'" }, + { name = "wrapt" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/65/3ada667d32675399001bf022ad3d9f3989b57101351ebc71d6fbe2384634/smart_open-7.6.1.tar.gz", hash = "sha256:4347996e7ba21db7cd1e059632e0b30395407e4f6c660d2ddffc8f2a9ae5f990", size = 54754, upload-time = "2026-05-09T06:23:37.06Z" } wheels = [ @@ -4211,12 +4217,12 @@ name = "statsmodels" version = "0.14.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "patsy", marker = "python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "patsy" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" } @@ -4337,7 +4343,7 @@ name = "tokenizers" version = "0.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "huggingface-hub", marker = "python_full_version >= '3.11'" }, + { name = "huggingface-hub" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } wheels = [ @@ -4908,6 +4914,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/a3/229851168ec3997f1ced60b93edbeb294a0c2b3af2d71143469371c05851/tree_sitter_verilog-1.0.3-cp39-abi3-win_arm64.whl", hash = "sha256:11576eaa43f89266ab8869fb8d2fb1c22c8da74aa8dc82e67259d6560635c68f", size = 749282, upload-time = "2024-11-10T23:35:30.602Z" }, ] +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" }, + { url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" }, +] + [[package]] name = "tree-sitter-zig" version = "1.1.2" @@ -4928,10 +4950,10 @@ name = "typer" version = "0.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc", marker = "python_full_version >= '3.11'" }, - { name = "click", marker = "python_full_version >= '3.11'" }, - { name = "rich", marker = "python_full_version >= '3.11'" }, - { name = "shellingham", marker = "python_full_version >= '3.11'" }, + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } wheels = [ @@ -4973,14 +4995,14 @@ name = "umap-learn" version = "0.5.12" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numba", marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "pynndescent", marker = "python_full_version < '3.13'" }, - { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numba" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "pynndescent" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/ee/af4171241117f85c74b5ca6448ea1033cc28d599c13651d67289bacd4083/umap_learn-0.5.12.tar.gz", hash = "sha256:6aff02ecac5f2aad9f3c65ee518d7ae93e1a985ae38721fdcffceee4232c33c7", size = 96672, upload-time = "2026-04-08T20:03:54.012Z" } wheels = [ @@ -5001,8 +5023,8 @@ name = "uvicorn" version = "0.47.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, - { name = "h11", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, + { name = "click" }, + { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/b1/8e7077a8641086aea449e1b5752a570f1b5906c64e0a33cd6d93b63a066b/uvicorn-0.47.0.tar.gz", hash = "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533", size = 90582, upload-time = "2026-05-14T18:16:54.455Z" }